沒看懂,多看幾遍吧html
1 簡介:
伸展樹,或者叫自適應查找樹,是一種用於保存有序集合的簡單高效的數據結構。伸展樹實質上是一個二叉查找樹。容許查找,插入,刪除,刪除最小,刪除最大,分割,合併等許多操做,這些操做的時間複雜度爲O(logN)。因爲伸展樹能夠適應需求序列,所以他們的性能在實際應用中更優秀。
伸展樹支持全部的二叉樹操做。伸展樹不保證最壞狀況下的時間複雜度爲O(logN)。伸展樹的時間複雜度邊界是均攤的。儘管一個單獨的操做可能很耗時,但對於一個任意的操做序列,時間複雜度能夠保證爲O(logN)。node
2 自調整和均攤分析:
平衡查找樹的一些限制:
一、平衡查找樹每一個節點都須要保存額外的信息。
二、難於實現,所以插入和刪除操做複雜度高,且是潛在的錯誤點。
三、對於簡單的輸入,性能並無什麼提升。
平衡查找樹能夠考慮提升性能的地方:
一、平衡查找樹在最差、平均和最壞狀況下的時間複雜度在本質上是相同的。
二、對一個節點的訪問,若是第二次訪問的時間小於第一次訪問,將是很是好的事情。
三、90-10法則。在實際狀況中,90%的訪問發生在10%的數據上。
四、處理好那90%的狀況就很好了。編程
3 均攤時間邊界:
在一顆二叉樹中訪問一個節點的時間複雜度是這個節點的深度。所以,咱們能夠重構樹的結構,使得被常常訪問的節點朝樹根的方向移動。儘管這會引入額外的操做,可是常常被訪問的節點被移動到了靠近根的位置,所以,對於這部分節點,咱們能夠很快的訪問。根據上面的90-10法則,這樣作能夠提升性能。
爲了達到上面的目的,咱們須要使用一種策略──旋轉到根(rotate-to-root)。具體實現以下:數據結構
旋轉分爲左旋和右旋,這兩個是對稱的。圖示:less
爲了敘述的方便,上圖的右旋叫作X繞Y右旋,左旋叫作Y繞X左旋。性能
下圖展現了將節點3旋轉到根:ui
首先節點3繞2左旋,而後3繞節點4右旋。
注意:所查找的數據必須符合上面的90-10法則,不然性能上不升反降!!this
4 基本的自底向上伸展樹:
應用伸展(splaying)技術,能夠獲得對數均攤邊界的時間複雜度。
在旋轉的時候,能夠分爲三種狀況:spa
一、zig狀況。code
X是查找路徑上咱們須要旋轉的一個非根節點。
若是X的父節點是根,那麼咱們用下圖所示的方法旋轉X到根:
這和一個普通的單旋轉相同。
二、zig-zag狀況。
在這種狀況中,X有一個父節點P和祖父節點G(P的父節點)。X是右子節點,P是左子節點,或者反過來。這個就是雙旋轉。
先是X繞P左旋轉,再接着X繞G右旋轉。
三、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到根上。
5 基本伸展樹操做:
一、插入:
當一個節點插入時,伸展操做將執行。所以,新插入的節點在根上。
二、查找:
若是查找成功(找到),那麼因爲伸展操做,被查找的節點成爲樹的新根。
若是查找失敗(沒有),那麼在查找遇到NULL以前的那個節點成爲新的根。也就是,若是查找的節點在樹中,那麼,此時根上的節點就是距離這個節點最近的節點。
三、查找最大最小:
查找以後執行伸展。
四、刪除最大最小:
a)刪除最小:
首先執行查找最小的操做。
這時,要刪除的節點就在根上。根據二叉查找樹的特色,根沒有左子節點。
使用根的右子結點做爲新的根,刪除舊的包含最小值的根。
b)刪除最大:
首先執行查找最大的操做。
刪除根,並把被刪除的根的左子結點做爲新的根。
五、刪除:
將要刪除的節點移至根。
刪除根,剩下兩個子樹L(左子樹)和R(右子樹)。
使用DeleteMax查找L的最大節點,此時,L的根沒有右子樹。
使R成爲L的根的右子樹。
以下圖示:
6 自頂向下的伸展樹:
在自底向上的伸展樹中,咱們須要求一個節點的父節點和祖父節點,所以這種伸展樹難以實現。所以,咱們能夠構建自頂向下的伸展樹。
當咱們沿着樹向下搜索某個節點X的時候,咱們將搜索路徑上的節點及其子樹移走。咱們構建兩棵臨時的樹──左樹和右樹。沒有被移走的節點構成的樹稱做中樹。在伸展操做的過程當中:
一、當前節點X是中樹的根。
二、左樹L保存小於X的節點。
三、右樹R保存大於X的節點。
開始時候,X是樹T的根,左右樹L和R都是空的。和前面的自下而上相同,自上而下也分三種狀況:
一、zig:
如上圖,在搜索到X的時候,所查找的節點比X小,將Y旋轉到中樹的樹根。旋轉以後,X及其右子樹被移動到右樹上。很顯然,右樹上的節點都大於所要查找的節點。注意X被放置在右樹的最小的位置,也就是X及其子樹比原先的右樹中全部的節點都要小。這是因爲越是在路徑前面被移動到右樹的節點,其值越大。讀者能夠分析一下樹的結構,緣由很簡單。
二、zig-zig:
在這種狀況下,所查找的節點在Z的子樹中,也就是,所查找的節點比X和Y都小。因此要將X,Y及其右子樹都移動到右樹中。首先是Y繞X右旋,而後Z繞Y右旋,最後將Z的右子樹(此時Z的右子節點爲Y)移動到右樹中。注意右樹中掛載點的位置。
三、zig-zag:
在這種狀況中,首先將Y右旋到根。這和Zig的狀況是同樣的。而後變成上圖右邊所示的形狀。接着,對Z進行左旋,將Y及其左子樹移動到左樹上。這樣,這種狀況就被分紅了兩個Zig狀況。這樣,在編程的時候就會簡化,可是操做的數目增長(至關於兩次Zig狀況)。
最後,在查找到節點後,將三棵樹合併。如圖:
將中樹的左右子樹分別鏈接到左樹的右子樹和右樹的左子樹上。將左右樹做爲X的左右子樹。從新最成了一所查找的節點爲根的樹。
下面給出僞代碼:
右鏈接:將當前根及其右子樹鏈接到右樹上。左子結點做爲新根。
左鏈接:將當前根及其左子樹鏈接到左樹上。右子結點做爲新根。
T : 當前的根節點。
1 Function Top-Down-Splay 2 Do 3 If X 小於 T Then 4 If X 等於 T 的左子結點 Then 5 右鏈接 6 ElseIf X 小於 T 的左子結點 Then 7 T的左子節點繞T右旋 8 右鏈接 9 Else X大於 T 的左子結點 Then 10 右鏈接 11 左鏈接 12 EndIf 13 ElseIf X大於 T Then 14 IF X 等於 T 的右子結點 Then 15 左鏈接 16 ElseIf X 大於 T 的右子結點 Then 17 T的右子節點繞T左旋 18 左鏈接 19 Else X小於 T 的右子結點‘ Then 20 左鏈接 21 右鏈接 22 EndIf 23 EndIf 24 While !(找到 X或遇到空節點) 25 組合左中右樹 26 EndFunction
一樣,上面的三種狀況也能夠簡化:
1 Function Top-Down-Splay 2 Do 3 If X 小於 T Then 4 If X 小於 T 的左孩子 Then 5 T的左子節點繞T右旋 6 EndIf 7 右鏈接 8 Else If X大於 T Then 9 If X 大於 T 的右孩子 Then 10 T的右子節點繞T左旋 11 EndIf 12 左鏈接 13 EndIf 14 While !(找到 X或遇到空節點) 15 組合左中右樹 16 EndFuntion
下面是一個查找節點19的例子:
在例子中,樹中並無節點19,最後,距離節點最近的節點18被旋轉到了根做爲新的根。節點20也是距離節點19最近的節點,可是節點20沒有成爲新根,這和節點20在原來樹中的位置有關係。
這個例子是查找節點c:
最後,給一個用C語言實現的例子:
1 /* 2 An implementation of top-down splaying 3 D. Sleator <sleator@cs.cmu.edu> 4 March 1992 5 */ 6 #include <stdlib.h> 7 #include <stdio.h> 8 int size; /* number of nodes in the tree */ 9 /* Not actually needed for any of the operations */ 10 typedef struct tree_node Tree; 11 struct tree_node 12 { 13 Tree * left, * right; 14 int item; 15 }; 16 17 Tree * splay (int i, Tree * t) 18 { 19 /* Simple top down splay, not requiring i to be in the tree t. */ 20 /* What it does is described above. */ 21 Tree N, *l, *r, *y; 22 if (t == NULL) 23 return t; 24 N.left = N.right = NULL; 25 l = r = &N; 26 for (;;) 27 { 28 if (i < t->item) 29 { 30 if (t->left == NULL) 31 { 32 break; 33 } 34 if (i < t->left->item) 35 { 36 y = t->left; /* rotate right */ 37 t->left = y->right; 38 y->right = t; 39 t = y; 40 if (t->left == NULL) 41 { 42 break; 43 } 44 } 45 r->left = t; /* link right */ 46 r = t; 47 t = t->left; 48 } 49 else if (i > t->item) 50 { 51 if (t->right == NULL) 52 { 53 break; 54 } 55 if (i > t->right->item) 56 { 57 y = t->right; /* rotate left */ 58 t->right = y->left; 59 y->left = t; 60 t = y; 61 if (t->right == NULL) 62 { 63 break; 64 } 65 } 66 l->right = t; /* link left */ 67 l = t; 68 t = t->right; 69 } 70 else 71 { 72 break; 73 } 74 } 75 l->right = t->left; /* assemble */ 76 r->left = t->right; 77 t->left = N.right; 78 t->right = N.left; 79 return t; 80 } 81 /* Here is how sedgewick would have written this. */ 82 /* It does the same thing. */ 83 Tree * sedgewickized_splay (int i, Tree * t) 84 { 85 Tree N, *l, *r, *y; 86 if (t == NULL) 87 { 88 return t; 89 } 90 N.left = N.right = NULL; 91 l = r = &N; 92 for (;;) 93 { 94 if (i < t->item) 95 { 96 if (t->left != NULL && i < t->left->item) 97 { 98 y = t->left; 99 t->left = y->right; 100 y->right = t; 101 t = y; 102 } 103 if (t->left == NULL) 104 { 105 break; 106 } 107 r->left = t; 108 r = t; 109 t = t->left; 110 } 111 else if (i > t->item) 112 { 113 if (t->right != NULL && i > t->right->item) 114 { 115 y = t->right; 116 t->right = y->left; 117 y->left = t; 118 t = y; 119 } 120 if (t->right == NULL) 121 { 122 break; 123 } 124 l->right = t; 125 l = t; 126 t = t->right; 127 } 128 else 129 { 130 break; 131 } 132 } 133 l->right=t->left; 134 r->left=t->right; 135 t->left=N.right; 136 t->right=N.left; 137 return t; 138 } 139 140 Tree * insert(int i, Tree * t) 141 { 142 /* Insert i into the tree t, unless it's already there. */ 143 /* Return a pointer to the resulting tree. */ 144 Tree * new; 145 146 new = (Tree *) malloc (sizeof (Tree)); 147 if (new == NULL) 148 { 149 printf("Ran out of space\n"); 150 exit(1); 151 } 152 new->item = i; 153 if (t == NULL) 154 { 155 new->left = new->right = NULL; 156 size = 1; 157 return new; 158 } 159 t = splay(i,t); 160 if (i < t->item) 161 { 162 new->left = t->left; 163 new->right = t; 164 t->left = NULL; 165 size ++; 166 return new; 167 } 168 else if (i > t->item) 169 { 170 new->right = t->right; 171 new->left = t; 172 t->right = NULL; 173 size++; 174 return new; 175 } 176 else 177 { 178 /* We get here if it's already in the tree */ 179 /* Don't add it again */ 180 free(new); 181 return t; 182 } 183 } 184 185 Tree * delete(int i, Tree * t) 186 { 187 /* Deletes i from the tree if it's there. */ 188 /* Return a pointer to the resulting tree. */ 189 Tree * x; 190 if (t==NULL) 191 { 192 return NULL; 193 } 194 t = splay(i,t); 195 if (i == t->item) 196 { /* found it */ 197 if (t->left == NULL) 198 { 199 x = t->right; 200 } 201 else 202 { 203 x = splay(i, t->left); 204 x->right = t->right; 205 } 206 size--; 207 free(t); 208 return x; 209 } 210 return t; /* It wasn't there */ 211 } 212 213 int main(int argv, char *argc[]) 214 { 215 /* A sample use of these functions. Start with the empty tree, */ 216 /* insert some stuff into it, and then delete it */ 217 Tree * root; 218 int i; 219 root = NULL; /* the empty tree */ 220 size = 0; 221 for (i = 0; i < 1024; i++) 222 { 223 root = insert((541*i) & (1023), root); 224 } 225 printf("size = %d\n", size); 226 for (i = 0; i < 1024; i++) 227 { 228 root = delete((541*i) & (1023), root); 229 } 230 printf("size = %d\n", size); 231 }
原文摘自:
http://www.cnblogs.com/kernel_hcy/archive/2010/03/17/1688360.html