伸展樹操作詳解 http://wenku.baidu.com/view/cc211f126edb6f1aff001f16.html?from=rec&pos=4&weight=9&lastweight=9&count=5
The Magical Splay http://wenku.baidu.com/view/a202e27931b765ce05081416.html
BST 拓展與伸展樹 (Splay) 一日通 http://wenku.baidu.com/view/f3fbe687b9d528ea81c779b6.html?from=rec&pos=4&weight=10&lastweight=10&count=5
********************************************************************************我是分割線*****************************************************************************************************************
一、簡介:
伸展樹,或者叫自適應尋找樹,是一種用於儲存有序集合的簡單高效的資料結構。伸展樹實質上是一個二叉尋找樹。允許尋找,插入,刪除,刪除最小,刪除最大,分割,合并等許多操作,這些操作的時間複雜度為O(logN)。由於伸展樹可以適應需求序列,因此他們的效能在實際應用中更優秀。
伸展樹支援所有的二叉樹操作。伸展樹不保證最壞情況下的時間複雜度為O(logN)。伸展樹的時間複雜度邊界是均攤的。儘管一個單獨的操作可能很耗時,但對於一個任意的操作序列,時間複雜度可以保證為O(logN)。
二、自調整和均攤分析:
平衡尋找樹的一些限制:
1、平衡尋找樹每個節點都需要儲存額外的資訊。
2、難於實現,因此插入和刪除操作複雜度高,且是潛在的錯誤點。
3、對於簡單的輸入,效能並沒有什麼提高。
平衡尋找樹可以考慮提高效能的地方:
1、平衡尋找樹在最差、平均和最壞情況下的時間複雜度在本質上是相同的。
2、對一個節點的訪問,如果第二次訪問的時間小於第一次訪問,將是非常好的事情。
3、90-10法則。在實際情況中,90%的訪問發生在10%的資料上。
4、處理好那90%的情況就很好了。
三、均攤時間邊界:
在一顆二叉樹中訪問一個節點的時間複雜度是這個節點的深度。因此,我們可以重構樹的結構,使得被經常訪問的節點朝樹根的方向移動。儘管這會引入額外的操作,但是經常被訪問的節點被移動到了靠近根的位置,因此,對於這部分節點,我們可以很快的訪問。根據上面的90-10法則,這樣做可以提高效能。
為了達到上面的目的,我們需要使用一種策略──旋轉到根(rotate-to-root)。具體實現如下:
旋轉分為左旋和右旋,這兩個是對稱的。圖示:
為了敘述的方便,上圖的右旋叫做X繞Y右旋,左旋叫做Y繞X左旋。
下圖展示了將節點3旋轉到根:
圖1
首先節點3繞2左旋,然後3繞節點4右旋。
注意:所尋找的資料必須符合上面的90-10法則,否則效能上不升反降。。
四、基本的自底向上伸展樹:
應用伸展(splaying)技術,可以得到對數均攤邊界的時間複雜度。
在旋轉的時候,可以分為三種情況:
1、zig情況。
X是尋找路徑上我們需要旋轉的一個非根節點。
如果X的父節點是根,那麼我們用下圖所示的方法旋轉X到根:
圖2
這和一個普通的單旋轉相同。
2、zig-zag情況。
在這種情況中,X有一個父節點P和祖父節點G(P的父節點)。X是右子節點,P是左子節點,或者反過來。這個就是雙旋轉。
先是X繞P左旋轉,再接著X繞G右旋轉。
如圖所示:
圖三
3、zig-zig情況。
這和前一個旋轉不同。在這種情況中,X和P都是左子節點或右子節點。
先是P繞G右旋轉,接著X繞P右旋轉。
如圖所示:
圖四
下面是splay的虛擬碼:
P(X) : 獲得X的父節點,G(X) : 獲得X的祖父節點(=P(P(X)))。
Function Buttom-up-splay:
Do
If X 是 P(X) 的左子結點 Then
If G(X) 為空白 Then
X 繞 P(X)右旋
Else If P(X)是G(X)的左子結點
P(X) 繞G(X)右旋
X 繞P(X)右旋
Else
X繞P(X)右旋
X繞P(X)左旋 (P(X)和上面一句的不同,是原來的G(X))
Endif
Else If X 是 P(X) 的右子結點 Then
If G(X) 為空白 Then
X 繞 P(X)左旋
Else If P(X)是G(X)的右子結點
P(X) 繞G(X)左旋
X 繞P(X)左旋
Else
X繞P(X)左旋
X繞P(X)右旋 (P(X)和上面一句的不同,是原來的G(X))
Endif
Endif
While (P(X) != NULL)
EndFunction
仔細分析zig-zag,可以發現,其實zig-zag就是兩次zig。因此上面的代碼可以簡化:
Function Buttom-up-splay:
Do
If X 是 P(X) 的左子結點 Then
If P(X)是G(X)的左子結點
P(X) 繞G(X)右旋
Endif
X 繞P(X)右旋
Else If X 是 P(X) 的右子結點 Then
If P(X)是G(X)的右子結點
P(X) 繞G(X)左旋
Endif
X 繞P(X)左旋
Endif
While (P(X) != NULL)
EndFunction
下面是一個例子,旋轉節點c到根上。
圖五
五、基本伸展樹操作:
1、插入:
當一個節點插入時,伸展操作將執行。因此,新插入的節點在根上。
2、尋找:
如果尋找成功(找到),那麼由於伸展操作,被尋找的節點成為樹的新根。
如果尋找失敗(沒有),那麼在尋找遇到NULL之前的那個節點成為新的根。也就是,如果尋找的節點在樹中,那麼,此時根上的節點就是距離這個節點最近的節點。
3、尋找最大最小:
尋找之後執行伸展。
4、刪除最大最小:
a)刪除最小:
首先執行尋找最小的操作。
這時,要刪除的節點就在根上。根據二叉尋找樹的特點,根沒有左子節點。
使用根的右子結點作為新的根,刪除舊的包含最小值的根。
b)刪除最大:
首先執行尋找最大的操作。
刪除根,並把被刪除的根的左子結點作為新的根。
5、刪除:
將要刪除的節點移至根。
刪除根,剩下兩個子樹L(左子樹)和R(右子樹)。
使用DeleteMax尋找L的最大節點,此時,L的根沒有右子樹。
使R成為L的根的右子樹。
如下圖示:
圖六
六、自頂向下的伸展樹:
在自底向上的伸展樹中,我們需要求一個節點的父節點和祖父節點,因此這種伸展樹難以實現。因此,我們可以構建自頂向下的伸展樹。
當我們沿著樹向下搜尋某個節點X的時候,我們將搜尋路徑上的節點及其子樹移走。我們構建兩棵臨時的樹──左樹和右樹。沒有被移走的節點構成的樹稱作中樹。在伸展操作的過程中:
1、當前節點X是中樹的根。
2、左樹L儲存小於X的節點。
3、右樹R儲存大於X的節點。
開始時候,X是樹T的根,左右樹L和R都是空的。和前面的自下而上相同,自上而下也分三種情況:
1、zig:
圖七
如上圖,在搜尋到X的時候,所尋找的節點