zoukankan      html  css  js  c++  java
  • 6天通吃树结构—— 第三天 Treap树

           

           我们知道,二叉查找树相对来说比较容易形成最坏的链表情况,所以前辈们想尽了各种优化策略,包括AVL,红黑,以及今天

    要讲的Treap树。

           Treap树算是一种简单的优化策略,这名字大家也能猜到,树和堆的合体,其实原理比较简单,在树中维护一个"优先级“,”优先级“

    采用随机数的方法,但是”优先级“必须满足根堆的性质,当然是“大根堆”或者“小根堆”都无所谓,比如下面的一棵树:

    从树中我们可以看到:

    ①:节点中的key满足“二叉查找树”。

    ②:节点中的“优先级”满足小根堆。

    一:基本操作

    1:定义

     1     #region Treap树节点
     2     /// <summary>
     3     /// Treap树
     4     /// </summary>
     5     /// <typeparam name="K"></typeparam>
     6     /// <typeparam name="V"></typeparam>
     7     public class TreapNode<K, V>
     8     {
     9         /// <summary>
    10         /// 节点元素
    11         /// </summary>
    12         public K key;
    13 
    14         /// <summary>
    15         /// 优先级(采用随机数)
    16         /// </summary>
    17         public int priority;
    18 
    19         /// <summary>
    20         /// 节点中的附加值
    21         /// </summary>
    22         public HashSet<V> attach = new HashSet<V>();
    23 
    24         /// <summary>
    25         /// 左节点
    26         /// </summary>
    27         public TreapNode<K, V> left;
    28 
    29         /// <summary>
    30         /// 右节点
    31         /// </summary>
    32         public TreapNode<K, V> right;
    33 
    34         public TreapNode() { }
    35 
    36         public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
    37         {
    38             //KV键值对
    39             this.key = key;
    40             this.priority = new Random(DateTime.Now.Millisecond).Next(0,int.MaxValue);
    41             this.attach.Add(value);
    42 
    43             this.left = left;
    44             this.right = right;
    45         }
    46     }
    47     #endregion

    节点里面定义了一个priority作为“堆定义”的旋转因子,因子采用“随机数“。

    2:添加

        首先我们知道各个节点的“优先级”是采用随机数的方法,那么就存在一个问题,当我们插入一个节点后,优先级不满足“堆定义"的

    时候我们该怎么办,前辈说此时需要旋转,直到满足堆定义为止。

    旋转有两种方式,如果大家玩转了AVL,那么对Treap中的旋转的理解轻而易举。

    ①: 左左情况旋转

    从图中可以看出,当我们插入“节点12”的时候,此时“堆性质”遭到破坏,必须进行旋转,我们发现优先级是6<9,所以就要进行

    左左情况旋转,最终也就形成了我们需要的结果。

    ②: 右右情况旋转

    既然理解了”左左情况旋转“,右右情况也是同样的道理,优先级中发现“6<9",进行”右右旋转“最终达到我们要的效果。

     1         #region 添加操作
     2         /// <summary>
     3         /// 添加操作
     4         /// </summary>
     5         /// <param name="key"></param>
     6         /// <param name="value"></param>
     7         public void Add(K key, V value)
     8         {
     9             node = Add(key, value, node);
    10         }
    11         #endregion
    12 
    13         #region 添加操作
    14         /// <summary>
    15         /// 添加操作
    16         /// </summary>
    17         /// <param name="key"></param>
    18         /// <param name="value"></param>
    19         /// <param name="tree"></param>
    20         /// <returns></returns>
    21         public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
    22         {
    23             if (tree == null)
    24                 tree = new TreapNode<K, V>(key, value, null, null);
    25 
    26             //左子树
    27             if (key.CompareTo(tree.key) < 0)
    28             {
    29                 tree.left = Add(key, value, tree.left);
    30 
    31                 //根据小根堆性质,需要”左左情况旋转”
    32                 if (tree.left.priority < tree.priority)
    33                 {
    34                     tree = RotateLL(tree);
    35                 }
    36             }
    37 
    38             //右子树
    39             if (key.CompareTo(tree.key) > 0)
    40             {
    41                 tree.right = Add(key, value, tree.right);
    42 
    43                 //根据小根堆性质,需要”右右情况旋转”
    44                 if (tree.right.priority < tree.priority)
    45                 {
    46                     tree = RotateRR(tree);
    47                 }
    48             }
    49 
    50             //将value追加到附加值中(也可对应重复元素)
    51             if (key.CompareTo(tree.key) == 0)
    52                 tree.attach.Add(value);
    53 
    54             return tree;
    55         }
    56         #endregion

    3:删除

      跟普通的二叉查找树一样,删除结点存在三种情况。

    ①:叶子结点

          跟普通查找树一样,直接释放本节点即可。

    ②:单孩子结点

         跟普通查找树一样操作。

    ③:满孩子结点

        其实在treap中删除满孩子结点有两种方式。

    第一种:跟普通的二叉查找树一样,找到“右子树”的最左结点(15),拷贝元素的值,但不拷贝元素的优先级,然后在右子树中

               删除“结点15”即可,最终效果如下图。

    第二种:将”结点下旋“,直到该节点不是”满孩子的情况“,该赋null的赋null,该将孩子结点顶上的就顶上,如下图:

    当然从理论上来说,第二种删除方法更合理,这里我写的就是第二种情况的代码。

     1         #region 删除当前树中的节点
     2         /// <summary>
     3         /// 删除当前树中的节点
     4         /// </summary>
     5         /// <param name="key"></param>
     6         /// <returns></returns>
     7         public void Remove(K key, V value)
     8         {
     9             node = Remove(key, value, node);
    10         }
    11         #endregion
    12 
    13         #region 删除当前树中的节点
    14         /// <summary>
    15         /// 删除当前树中的节点
    16         /// </summary>
    17         /// <param name="key"></param>
    18         /// <param name="tree"></param>
    19         /// <returns></returns>
    20         public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
    21         {
    22             if (tree == null)
    23                 return null;
    24 
    25             //左子树
    26             if (key.CompareTo(tree.key) < 0)
    27             {
    28                 tree.left = Remove(key, value, tree.left);
    29             }
    30             //右子树
    31             if (key.CompareTo(tree.key) > 0)
    32             {
    33                 tree.right = Remove(key, value, tree.right);
    34             }
    35             /*相等的情况*/
    36             if (key.CompareTo(tree.key) == 0)
    37             {
    38                 //判断里面的HashSet是否有多值
    39                 if (tree.attach.Count > 1)
    40                 {
    41                     //实现惰性删除
    42                     tree.attach.Remove(value);
    43                 }
    44                 else
    45                 {
    46                     //有两个孩子的情况
    47                     if (tree.left != null && tree.right != null)
    48                     {
    49                         //如果左孩子的优先级低就需要“左旋”
    50                         if (tree.left.priority < tree.right.priority)
    51                         {
    52                             tree = RotateLL(tree);
    53                         }
    54                         else
    55                         {
    56                             //否则“右旋”
    57                             tree = RotateRR(tree);
    58                         }
    59 
    60                         //继续旋转
    61                         tree = Remove(key, value, tree);
    62                     }
    63                     else
    64                     {
    65                         //如果旋转后已经变成了叶子节点则直接删除
    66                         if (tree == null)
    67                             return null;
    68 
    69                         //最后就是单支树
    70                         tree = tree.left == null ? tree.right : tree.left;
    71                     }
    72                 }
    73             }
    74 
    75             return tree;
    76         }
    77         #endregion

    4:总结

    treap树在CURD中是期望的logN,由于我们加了”优先级“,所以会出现”链表“的情况几乎不存在,但是他的Add和Remove相比严格的

    平衡二叉树有更少的旋转操作,可以说性能是在”普通二叉树“和”平衡二叉树“之间。

    最后是总运行代码,不过这里我就不做测试了。

    View Code
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 
      6 namespace DataStruct
      7 {
      8     #region Treap树节点
      9     /// <summary>
     10     /// Treap树
     11     /// </summary>
     12     /// <typeparam name="K"></typeparam>
     13     /// <typeparam name="V"></typeparam>
     14     public class TreapNode<K, V>
     15     {
     16         /// <summary>
     17         /// 节点元素
     18         /// </summary>
     19         public K key;
     20 
     21         /// <summary>
     22         /// 优先级(采用随机数)
     23         /// </summary>
     24         public int priority;
     25 
     26         /// <summary>
     27         /// 节点中的附加值
     28         /// </summary>
     29         public HashSet<V> attach = new HashSet<V>();
     30 
     31         /// <summary>
     32         /// 左节点
     33         /// </summary>
     34         public TreapNode<K, V> left;
     35 
     36         /// <summary>
     37         /// 右节点
     38         /// </summary>
     39         public TreapNode<K, V> right;
     40 
     41         public TreapNode() { }
     42 
     43         public TreapNode(K key, V value, TreapNode<K, V> left, TreapNode<K, V> right)
     44         {
     45             //KV键值对
     46             this.key = key;
     47             this.priority = new Random(DateTime.Now.Millisecond).Next(0,int.MaxValue);
     48             this.attach.Add(value);
     49 
     50             this.left = left;
     51             this.right = right;
     52         }
     53     }
     54     #endregion
     55 
     56     public class TreapTree<K, V> where K : IComparable
     57     {
     58         public TreapNode<K, V> node = null;
     59 
     60         #region 添加操作
     61         /// <summary>
     62         /// 添加操作
     63         /// </summary>
     64         /// <param name="key"></param>
     65         /// <param name="value"></param>
     66         public void Add(K key, V value)
     67         {
     68             node = Add(key, value, node);
     69         }
     70         #endregion
     71 
     72         #region 添加操作
     73         /// <summary>
     74         /// 添加操作
     75         /// </summary>
     76         /// <param name="key"></param>
     77         /// <param name="value"></param>
     78         /// <param name="tree"></param>
     79         /// <returns></returns>
     80         public TreapNode<K, V> Add(K key, V value, TreapNode<K, V> tree)
     81         {
     82             if (tree == null)
     83                 tree = new TreapNode<K, V>(key, value, null, null);
     84 
     85             //左子树
     86             if (key.CompareTo(tree.key) < 0)
     87             {
     88                 tree.left = Add(key, value, tree.left);
     89 
     90                 //根据小根堆性质,需要”左左情况旋转”
     91                 if (tree.left.priority < tree.priority)
     92                 {
     93                     tree = RotateLL(tree);
     94                 }
     95             }
     96 
     97             //右子树
     98             if (key.CompareTo(tree.key) > 0)
     99             {
    100                 tree.right = Add(key, value, tree.right);
    101 
    102                 //根据小根堆性质,需要”右右情况旋转”
    103                 if (tree.right.priority < tree.priority)
    104                 {
    105                     tree = RotateRR(tree);
    106                 }
    107             }
    108 
    109             //将value追加到附加值中(也可对应重复元素)
    110             if (key.CompareTo(tree.key) == 0)
    111                 tree.attach.Add(value);
    112 
    113             return tree;
    114         }
    115         #endregion
    116 
    117         #region 第一种:左左旋转(单旋转)
    118         /// <summary>
    119         /// 第一种:左左旋转(单旋转)
    120         /// </summary>
    121         /// <param name="node"></param>
    122         /// <returns></returns>
    123         public TreapNode<K, V> RotateLL(TreapNode<K, V> node)
    124         {
    125             //top:需要作为顶级节点的元素
    126             var top = node.left;
    127 
    128             //先截断当前节点的左孩子
    129             node.left = top.right;
    130 
    131             //将当前节点作为temp的右孩子
    132             top.right = node;
    133 
    134             return top;
    135         }
    136         #endregion
    137 
    138         #region 第二种:右右旋转(单旋转)
    139         /// <summary>
    140         /// 第二种:右右旋转(单旋转)
    141         /// </summary>
    142         /// <param name="node"></param>
    143         /// <returns></returns>
    144         public TreapNode<K, V> RotateRR(TreapNode<K, V> node)
    145         {
    146             //top:需要作为顶级节点的元素
    147             var top = node.right;
    148 
    149             //先截断当前节点的右孩子
    150             node.right = top.left;
    151 
    152             //将当前节点作为temp的右孩子
    153             top.left = node;
    154 
    155             return top;
    156         }
    157         #endregion
    158 
    159         #region 树的指定范围查找
    160         /// <summary>
    161         /// 树的指定范围查找
    162         /// </summary>
    163         /// <param name="min"></param>
    164         /// <param name="max"></param>
    165         /// <returns></returns>
    166         public HashSet<V> SearchRange(K min, K max)
    167         {
    168             HashSet<V> hashSet = new HashSet<V>();
    169 
    170             hashSet = SearchRange(min, max, hashSet, node);
    171 
    172             return hashSet;
    173         }
    174         #endregion
    175 
    176         #region 树的指定范围查找
    177         /// <summary>
    178         /// 树的指定范围查找
    179         /// </summary>
    180         /// <param name="range1"></param>
    181         /// <param name="range2"></param>
    182         /// <param name="tree"></param>
    183         /// <returns></returns>
    184         public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, TreapNode<K, V> tree)
    185         {
    186             if (tree == null)
    187                 return hashSet;
    188 
    189             //遍历左子树(寻找下界)
    190             if (min.CompareTo(tree.key) < 0)
    191                 SearchRange(min, max, hashSet, tree.left);
    192 
    193             //当前节点是否在选定范围内
    194             if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
    195             {
    196                 //等于这种情况
    197                 foreach (var item in tree.attach)
    198                     hashSet.Add(item);
    199             }
    200 
    201             //遍历右子树(两种情况:①:找min的下限 ②:必须在Max范围之内)
    202             if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
    203                 SearchRange(min, max, hashSet, tree.right);
    204 
    205             return hashSet;
    206         }
    207         #endregion
    208 
    209         #region 找到当前树的最小节点
    210         /// <summary>
    211         /// 找到当前树的最小节点
    212         /// </summary>
    213         /// <returns></returns>
    214         public TreapNode<K, V> FindMin()
    215         {
    216             return FindMin(node);
    217         }
    218         #endregion
    219 
    220         #region 找到当前树的最小节点
    221         /// <summary>
    222         /// 找到当前树的最小节点
    223         /// </summary>
    224         /// <param name="tree"></param>
    225         /// <returns></returns>
    226         public TreapNode<K, V> FindMin(TreapNode<K, V> tree)
    227         {
    228             if (tree == null)
    229                 return null;
    230 
    231             if (tree.left == null)
    232                 return tree;
    233 
    234             return FindMin(tree.left);
    235         }
    236         #endregion
    237 
    238         #region 找到当前树的最大节点
    239         /// <summary>
    240         /// 找到当前树的最大节点
    241         /// </summary>
    242         /// <returns></returns>
    243         public TreapNode<K, V> FindMax()
    244         {
    245             return FindMin(node);
    246         }
    247         #endregion
    248 
    249         #region 找到当前树的最大节点
    250         /// <summary>
    251         /// 找到当前树的最大节点
    252         /// </summary>
    253         /// <param name="tree"></param>
    254         /// <returns></returns>
    255         public TreapNode<K, V> FindMax(TreapNode<K, V> tree)
    256         {
    257             if (tree == null)
    258                 return null;
    259 
    260             if (tree.right == null)
    261                 return tree;
    262 
    263             return FindMax(tree.right);
    264         }
    265         #endregion
    266 
    267         #region 删除当前树中的节点
    268         /// <summary>
    269         /// 删除当前树中的节点
    270         /// </summary>
    271         /// <param name="key"></param>
    272         /// <returns></returns>
    273         public void Remove(K key, V value)
    274         {
    275             node = Remove(key, value, node);
    276         }
    277         #endregion
    278 
    279         #region 删除当前树中的节点
    280         /// <summary>
    281         /// 删除当前树中的节点
    282         /// </summary>
    283         /// <param name="key"></param>
    284         /// <param name="tree"></param>
    285         /// <returns></returns>
    286         public TreapNode<K, V> Remove(K key, V value, TreapNode<K, V> tree)
    287         {
    288             if (tree == null)
    289                 return null;
    290 
    291             //左子树
    292             if (key.CompareTo(tree.key) < 0)
    293             {
    294                 tree.left = Remove(key, value, tree.left);
    295             }
    296             //右子树
    297             if (key.CompareTo(tree.key) > 0)
    298             {
    299                 tree.right = Remove(key, value, tree.right);
    300             }
    301             /*相等的情况*/
    302             if (key.CompareTo(tree.key) == 0)
    303             {
    304                 //判断里面的HashSet是否有多值
    305                 if (tree.attach.Count > 1)
    306                 {
    307                     //实现惰性删除
    308                     tree.attach.Remove(value);
    309                 }
    310                 else
    311                 {
    312                     //有两个孩子的情况
    313                     if (tree.left != null && tree.right != null)
    314                     {
    315                         //如果左孩子的优先级低就需要“左旋”
    316                         if (tree.left.priority < tree.right.priority)
    317                         {
    318                             tree = RotateLL(tree);
    319                         }
    320                         else
    321                         {
    322                             //否则“右旋”
    323                             tree = RotateRR(tree);
    324                         }
    325 
    326                         //继续旋转
    327                         tree = Remove(key, value, tree);
    328                     }
    329                     else
    330                     {
    331                         //如果旋转后已经变成了叶子节点则直接删除
    332                         if (tree == null)
    333                             return null;
    334 
    335                         //最后就是单支树
    336                         tree = tree.left == null ? tree.right : tree.left;
    337                     }
    338                 }
    339             }
    340 
    341             return tree;
    342         }
    343         #endregion
    344     }
    345 }
  • 相关阅读:
    用perfmon简单分析GDI+性能和代码的一点小改进
    从clr profiler的角度看string concat 和stringbuilder的性能差别
    CLR兴趣小组第一次活动总结
    C#3.0看起来比较酷的咚咚
    windbg入门及提高(广告贴)
    济南.NET技术俱乐部8月份活动预告
    WebService安全性的问题
    Basic Windbg 4.Out Of Memory的分析及诊断方法
    A question about C++ static method and C# static method
    内存泄露的小问题
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/2614484.html
Copyright © 2011-2022 走看看