首页 > 代码库 > 6天通吃树结构—— 第四天 伸展树

6天通吃树结构—— 第四天 伸展树

原文: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                 else55                 {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             else20             {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                 else36                     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                     else47                         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             else23             {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         #endregion114 115         #region 判断是否为空116         /// <summary>117         /// 判断是否为空118         /// </summary>119         /// <returns></returns>120         public bool isEmpty()121         {122             return root == nullNode;123         }124         #endregion125 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                 else180                 {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         #endregion202 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             else225             {226                 newTree = root.left;227 228                 newTree = Splay(Key, newTree);229 230                 newTree.right = root.right;231             }232             root = newTree;233         }234         #endregion235 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         #endregion250 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         #endregion265     }266 }

伸展树可以总结成一幅图:

技术分享

6天通吃树结构—— 第四天 伸展树