6天通吃樹結構—— 第四天 伸展樹


 

      我們知道AVL樹為了保持嚴格的平衡,所以在數據插入上會呈現過多的旋轉,影響了插入和刪除的性能,此時AVL的一個變種

伸展樹(Splay)就應運而生了,我們知道萬事萬物都遵循一個“八二原則“,也就是說80%的人只會用到20%的數據,比如說我們

的“QQ輸入法”,平常打的字也就那么多,或許還沒有20%呢。

 

一:伸展樹

 1:思想

    伸展樹的原理就是這樣的一個”八二原則”,比如我要查詢樹中的“節點7”,如果我們是AVL的思路,每次都查詢“節點7”,那么當這

棵樹中的節點越來越多的情況下就會呈現下旋,所以復雜度只會遞增,伸展樹的想法就是在第一次查詢時樹里面會經過一陣痙攣把

“節點7”頂成“根節點”,操作類似AVL的雙旋轉,比如下圖:

當我們再次查詢同樣的”數字7“時,直接在根節點處O(1)取出,當然這算是一個最理想的情況,有時痙攣過度,會出現糟糕的”鏈表“,

也就退化了到O(N),所以伸展樹講究的是”攤還時間“,意思就是說在”連續的一系列操作中的平均時間“,當然可以保證是log(N)。

 

2:伸展方式

    不知道大家可否記得,在AVL中的旋轉要分4個情況,同樣伸展樹中的伸展需要考慮6種情況,當然不考慮鏡像的話也就是3種情況,

從樹的伸展方向上來說有“自下而上”和“自上而下"的兩種方式,考慮到代碼實現簡潔,我還是說下后者。

 

<1> 自上而下的伸展

      這種伸展方式會把樹切成三份,L樹,M樹,R樹,考慮的情況有:單旋轉,“一字型”旋轉,“之字形”旋轉。

①: 單旋轉

從圖中我們可以看到,要將“節點2”插入到根上,需要將接近於“節點2”的數插入到根上,也就是這里的“節點7”,首先樹被分成了3份,

初始情況,L和R樹是“空節點”,M是整棵樹,現在需要我們一步一步拆分,當我們將“節點2”試插入到“節點7”的左孩子時,發現“節點7”

就是父節點,滿足“單旋轉”情況,然后我們將整棵樹放到“R樹”中的left節點上,M此時是一個邏輯上的空節點,然后我們將R樹追加到

M樹中。L樹追加到M的左子樹中,最后我們將“節點2”插入到根節點上。說這么多有點拗口,伸展樹比較難懂,需要大家仔細品味一下。

 

②: 一字型

一字型旋轉方式與我們AVL中的“單旋轉”類似,首先同樣我們切成了三份,當我們"預插入20時”,發現20的“父節點”是根的右孩子,

而我們要插入的數字又在父節點的右邊,此時滿足”一字型“旋轉,我們將7,10兩個節點按照”右右情況”旋轉,旋轉后“節點10"的

左孩子放入到L樹的right節點,"節點10”作為中間樹M,最后將20插入根節點。

③: 之字形

 

之字形有點類似AVL中的“雙旋轉”,不過人家采取的策略是不一樣的,當我們試插入“節點9”,同樣發現“父節點”是根的右兒子,並且

“節點9”要插入到父節點的內側,根據規則,需要將“父節點10”作為M樹中的根節點,“節點7”作為L樹中的right節點,然后M拼接L和R,

最后將節點9插入到根上。

 

3:基本操作

①:節點定義

我們還是采用普通二叉樹中的節點定義,也就沒有了AVL那么煩人的高度信息。

 1     public class BinaryNode<T>
 2     {
 3         // Constructors
 4         public BinaryNode(T theElement) : this(theElement, null, null) { }
 5 
 6         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)
 7         {
 8             element = theElement;
 9             left = lt;
10             right = rt;
11         }
12 
13         public T element;
14 
15         public BinaryNode<T> left;
16 
17         public BinaryNode<T> right;
18     }

②:伸展

     這里為了編寫代碼方便,我采用的是邏輯nullNode節點,具體伸展邏輯大家可以看上面的圖。

 1         #region 伸展
 2         /// <summary>
 3         /// 伸展
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         /// <param name="tree"></param>
 7         /// <returns></returns>
 8         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)
 9         {
10             BinaryNode<T> leftTreeMax, rightTreeMin;
11 
12             header.left = header.right = nullNode;
13 
14             leftTreeMax = rightTreeMin = header;
15 
16             nullNode.element = Key;
17 
18             while (true)
19             {
20                 int compareResult = Key.CompareTo(tree.element);
21 
22                 if (compareResult < 0)
23                 {
24                     //如果成立,說明是”一字型“旋轉
25                     if (Key.CompareTo(tree.left.element) < 0)
26                         tree = rotateWithLeftChild(tree);
27 
28                     if (tree.left == nullNode)
29                         break;
30 
31                     //動態的將中間樹的”當前節點“追加到 R 樹中,同時備份在header中
32                     rightTreeMin.left = tree;
33 
34                     rightTreeMin = tree;
35 
36                     tree = tree.left;
37                 }
38                 else if (compareResult > 0)
39                 {
40                     //如果成立,說明是”一字型“旋轉
41                     if (Key.CompareTo(tree.right.element) > 0)
42                         tree = rotateWithRightChild(tree);
43 
44                     if (tree.right == nullNode)
45                         break;
46 
47                     //動態的將中間樹的”當前節點“追加到 L 樹中,同時備份在header中
48                     leftTreeMax.right = tree;
49 
50                     leftTreeMax = tree;
51 
52                     tree = tree.right;
53                 }
54                 else
55                 {
56                     break;
57                 }
58             }
59 
60             /* 剝到最后一層,來最后一次切分 */
61             //把中間樹的左孩子給“左樹”
62             leftTreeMax.right = tree.left;
63 
64             //把中間樹的右孩子給“右樹”
65             rightTreeMin.left = tree.right;
66 
67             /* 合並操作 */
68             //將頭節點的左樹作為中間樹的左孩子
69             tree.left = header.right;
70 
71             //將頭結點的右樹作為中間樹的右孩子
72             tree.right = header.left;
73 
74             return tree;
75         }
76         #endregion

③:插入

插入操作關鍵在於我們要找到接近於”要插入點“的節點,然后頂成“根節點”,也就是上面三分圖中的最后一分。

 1 #region 插入
 2         /// <summary>
 3         /// 插入
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         public void Insert(T Key)
 7         {
 8             if (newNode == null)
 9                 newNode = new BinaryNode<T>(default(T));
10 
11             newNode.element = Key;
12 
13             if (root == nullNode)
14             {
15                 newNode.left = newNode.right = nullNode;
16 
17                 root = newNode;
18             }
19             else
20             {
21                 root = Splay(Key, root);
22 
23                 int compareResult = Key.CompareTo(root.element);
24 
25                 if (compareResult < 0)
26                 {
27                     newNode.left = root.left;
28 
29                     newNode.right = root;
30 
31                     root.left = nullNode;
32 
33                     root = newNode;
34                 }
35                 else
36                     if (compareResult > 0)
37                     {
38                         newNode.right = root.right;
39 
40                         newNode.left = root;
41 
42                         root.right = nullNode;
43 
44                         root = newNode;
45                     }
46                     else
47                         return;
48             }
49 
50             newNode = null;
51         }
52         #endregion

④:刪除

  刪除操作也要將節點伸展到根上,然后進行刪除,邏輯很簡單。

 1  #region 刪除
 2         /// <summary>
 3         /// 刪除
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         public void Remove(T Key)
 7         {
 8             BinaryNode<T> newTree;
 9 
10             //將刪除結點頂到根節點
11             root = Splay(Key, root);
12 
13             //不等於說明沒有找到
14             if (root.element.CompareTo(Key) != 0)
15                 return;
16 
17             //如果左邊為空,則直接用root的右孩子接上去
18             if (root.left == nullNode)
19             {
20                 newTree = root.right;
21             }
22             else
23             {
24                 newTree = root.left;
25 
26                 newTree = Splay(Key, newTree);
27 
28                 newTree.right = root.right;
29             }
30             root = newTree;
31         }
32         #endregion

 

總的運行代碼如下:

View Code
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 
  6 namespace DataStructSplay
  7 {
  8     public class BinaryNode<T>
  9     {
 10         public BinaryNode(T theElement) : this(theElement, null, null) { }
 11 
 12         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)
 13         {
 14             element = theElement;
 15             left = lt;
 16             right = rt;
 17         }
 18 
 19         public T element;
 20 
 21         public BinaryNode<T> left;
 22 
 23         public BinaryNode<T> right;
 24     }
 25 
 26     public class SplayTree<T> where T : IComparable
 27     {
 28         public BinaryNode<T> root;
 29 
 30         public BinaryNode<T> nullNode;
 31 
 32         public BinaryNode<T> header = new BinaryNode<T>(default(T));
 33 
 34         public BinaryNode<T> newNode;
 35 
 36         public SplayTree()
 37         {
 38             nullNode = new BinaryNode<T>(default(T));
 39 
 40             nullNode.left = nullNode.right = nullNode;
 41 
 42             root = nullNode;
 43         }
 44 
 45         #region 插入
 46         /// <summary>
 47         /// 插入
 48         /// </summary>
 49         /// <param name="Key"></param>
 50         public void Insert(T Key)
 51         {
 52             if (newNode == null)
 53                 newNode = new BinaryNode<T>(default(T));
 54 
 55             newNode.element = Key;
 56 
 57             if (root == nullNode)
 58             {
 59                 newNode.left = newNode.right = nullNode;
 60 
 61                 root = newNode;
 62             }
 63             else
 64             {
 65                 root = Splay(Key, root);
 66 
 67                 int compareResult = Key.CompareTo(root.element);
 68 
 69                 if (compareResult < 0)
 70                 {
 71                     newNode.left = root.left;
 72 
 73                     newNode.right = root;
 74 
 75                     root.left = nullNode;
 76 
 77                     root = newNode;
 78                 }
 79                 else
 80                     if (compareResult > 0)
 81                     {
 82                         newNode.right = root.right;
 83 
 84                         newNode.left = root;
 85 
 86                         root.right = nullNode;
 87 
 88                         root = newNode;
 89                     }
 90                     else
 91                         return;
 92             }
 93 
 94             newNode = null;
 95         }
 96         #endregion
 97 
 98         #region 是否包含
 99         /// <summary>
100         /// 是否包含
101         /// </summary>
102         /// <param name="Key"></param>
103         /// <returns></returns>
104         public bool Contains(T Key)
105         {
106             if (isEmpty())
107                 return false;
108 
109             root = Splay(Key, root);
110 
111             return root.element.CompareTo(Key) == 0;
112         }
113         #endregion
114 
115         #region 判斷是否為空
116         /// <summary>
117         /// 判斷是否為空
118         /// </summary>
119         /// <returns></returns>
120         public bool isEmpty()
121         {
122             return root == nullNode;
123         }
124         #endregion
125 
126         #region 伸展
127         /// <summary>
128         /// 伸展
129         /// </summary>
130         /// <param name="Key"></param>
131         /// <param name="tree"></param>
132         /// <returns></returns>
133         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)
134         {
135             BinaryNode<T> leftTreeMax, rightTreeMin;
136 
137             header.left = header.right = nullNode;
138 
139             leftTreeMax = rightTreeMin = header;
140 
141             nullNode.element = Key;
142 
143             while (true)
144             {
145                 int compareResult = Key.CompareTo(tree.element);
146 
147                 if (compareResult < 0)
148                 {
149                     //如果成立,說明是”一字型“旋轉
150                     if (Key.CompareTo(tree.left.element) < 0)
151                         tree = rotateWithLeftChild(tree);
152 
153                     if (tree.left == nullNode)
154                         break;
155 
156                     //動態的將中間樹的”當前節點“追加到 R 樹中,同時備份在header中
157                     rightTreeMin.left = tree;
158 
159                     rightTreeMin = tree;
160 
161                     tree = tree.left;
162                 }
163                 else if (compareResult > 0)
164                 {
165                     //如果成立,說明是”一字型“旋轉
166                     if (Key.CompareTo(tree.right.element) > 0)
167                         tree = rotateWithRightChild(tree);
168 
169                     if (tree.right == nullNode)
170                         break;
171 
172                     //動態的將中間樹的”當前節點“追加到 L 樹中,同時備份在header中
173                     leftTreeMax.right = tree;
174 
175                     leftTreeMax = tree;
176 
177                     tree = tree.right;
178                 }
179                 else
180                 {
181                     break;
182                 }
183             }
184 
185             /* 剝到最后一層,來最后一次切分 */
186             //把中間樹的左孩子給“左樹”
187             leftTreeMax.right = tree.left;
188 
189             //把中間樹的右孩子給“右樹”
190             rightTreeMin.left = tree.right;
191 
192             /* 合並操作 */
193             //將頭節點的左樹作為中間樹的左孩子
194             tree.left = header.right;
195 
196             //將頭結點的右樹作為中間樹的右孩子
197             tree.right = header.left;
198 
199             return tree;
200         }
201         #endregion
202 
203         #region 刪除
204         /// <summary>
205         /// 刪除
206         /// </summary>
207         /// <param name="Key"></param>
208         public void Remove(T Key)
209         {
210             BinaryNode<T> newTree;
211 
212             //將刪除結點頂到根節點
213             root = Splay(Key, root);
214 
215             //不等於說明沒有找到
216             if (root.element.CompareTo(Key) != 0)
217                 return;
218 
219             //如果左邊為空,則直接用root的右孩子接上去
220             if (root.left == nullNode)
221             {
222                 newTree = root.right;
223             }
224             else
225             {
226                 newTree = root.left;
227 
228                 newTree = Splay(Key, newTree);
229 
230                 newTree.right = root.right;
231             }
232             root = newTree;
233         }
234         #endregion
235 
236         #region 右旋轉
237         /// <summary>
238         /// 右旋轉
239         /// </summary>
240         /// <param name="k1"></param>
241         /// <returns></returns>
242         public BinaryNode<T> rotateWithRightChild(BinaryNode<T> k1)
243         {
244             BinaryNode<T> k2 = k1.right;
245             k1.right = k2.left;
246             k2.left = k1;
247             return k2;
248         }
249         #endregion
250 
251         #region 左旋轉
252         /// <summary>
253         /// 左旋轉
254         /// </summary>
255         /// <param name="k2"></param>
256         /// <returns></returns>
257         public BinaryNode<T> rotateWithLeftChild(BinaryNode<T> k2)
258         {
259             BinaryNode<T> k1 = k2.left;
260             k2.left = k1.right;
261             k1.right = k2;
262             return k1;
263         }
264         #endregion
265     }
266 }

伸展樹可以總結成一幅圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM