我們討論過,樹的搜索效率與樹的深度有關。二叉搜索樹的深度可能為n,這種情況下,每次搜索的復雜度為n的量級。AVL樹通過動態平衡樹的深度,單次搜索的復雜度為log(n)。我們下面看伸展樹(splay tree),它對於m次連續搜索操作有很好的效率。伸展樹會在一次搜索后,對樹進行一些特殊的操作。這些操作的理念與AVL樹有些類似,即通過旋轉,來改變樹節點的分布,並減小樹的深度。但伸展樹並沒有AVL的平衡要求,任意節點的左右子樹可以相差任意深度。與二叉搜索樹類似,伸展樹的單次搜索也可能需要n次操作。但伸展樹可以保證,m次的連續搜索操作的復雜度為mlog(n)的量級,而不是mn量級。
伸展樹的出發點是這樣的:考慮到局部性原理(剛被訪問的內容下次可能仍會被訪問,查找次數多的內容可能下一次會被訪問),為了使整個查找時間更小, 被查頻率高的那些節點應當經常處於靠近樹根的位置。這樣,很容易得想到以下這個方案:每次查找節點之后對樹進行重構,把被查找的節點搬移到樹根,這種自調整形式的二叉查找樹就是伸展樹。每次對伸展樹進行操作后,它均會通過旋轉的方法把被訪問節點旋轉到樹根的位置。為了將當前被訪問節點旋轉到樹根,我們通常將節點自底向上旋轉,直至該節點成為樹根為止。“旋轉”的巧妙之處就是在不打亂數列中數據大小關系(指中序遍歷結果是全序的)情況下,所有基本操作的平攤復雜度仍為O(log n)。
伸展樹主要有三種旋轉操作,分別為單旋轉,一字形旋轉和之字形旋轉。為了便於解釋,我們假設當前被訪問節點為X,X的父親節點為Y(如果X的父親節點存在),X的祖父節點為Z(如果X的祖父節點存在)。具體來說,在查詢到目標節點后,伸展樹會不斷進行下面三種操作中的一個,直到目標節點成為根節點 (注意,祖父節點是指父節點的父節點)
1. zig: 當目標節點是根節點的左子節點或右子節點時,進行一次單旋轉,將目標節點調整到根節點的位置。
2. zig-zag: 當目標節點、父節點和祖父節點成"zig-zag"構型時,進行一次雙旋轉,將目標節點調整到祖父節點的位置。
3. zig-zig:當目標節點、父節點和祖父節點成"zig-zig"構型時,進行一次zig-zig操作,將目標節點調整到祖父節點的位置。
單旋轉操作和雙旋轉操作見AVL樹。下面是zig-zig操作的示意圖:
zig-zig operation
在伸展樹中,zig-zig操作(基本上)取代了AVL樹中的單旋轉。通常來說,如果上面的樹是失衡的,那么A、B子樹很可能深度比較大。相對於單旋轉(想一下單旋轉的效果),zig-zig可以將A、B子樹放在比較高的位置,從而減小樹總的深度。
下面我們用一個具體的例子示范。我們將從樹中搜索節點2:
Original
zig-zag (double rotation)
zig-zig
zig (single rotation at root)
伸展樹的另一個好處是將最近搜索的節點放在最容易搜索的根節點的位置。在許多應用環境中,比如網絡應用中,某些固定內容會被大量重復訪問。伸展樹可以讓這種重復搜索以很高的效率完成。