伸展樹

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         }

③:插入

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

 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         }

④:刪除

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

 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         }
發佈了29 篇原創文章 · 獲贊 19 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章