zoukankan      html  css  js  c++  java
  • 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 }

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

  • 相关阅读:
    jsack
    生产BackPressure 的代码
    org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint
    https://www.callicoder.com/java-8-completablefuture-tutorial/
    microservices kubernetes
    flink metrics
    numRecordsIn 在哪里实现?
    flink Job提交过程
    https://jzh.12333sh.gov.cn/jzh/
    blocking
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2623455.html
Copyright © 2011-2022 走看看