首页 > 代码库 > 6天通吃树结构—— 第三天 Treap树

6天通吃树结构—— 第三天 Treap树

原文: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         #endregion12 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         #endregion12 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                 else45                 {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                         else55                         {56                             //否则“右旋”57                             tree = RotateRR(tree);58                         }59 60                         //继续旋转61                         tree = Remove(key, value, tree);62                     }63                     else64                     {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         #endregion116 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         #endregion137 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         #endregion158 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         #endregion175 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         #endregion208 209         #region 找到当前树的最小节点210         /// <summary>211         /// 找到当前树的最小节点212         /// </summary>213         /// <returns></returns>214         public TreapNode<K, V> FindMin()215         {216             return FindMin(node);217         }218         #endregion219 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         #endregion237 238         #region 找到当前树的最大节点239         /// <summary>240         /// 找到当前树的最大节点241         /// </summary>242         /// <returns></returns>243         public TreapNode<K, V> FindMax()244         {245             return FindMin(node);246         }247         #endregion248 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         #endregion266 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         #endregion278 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                 else311                 {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                         else321                         {322                             //否则“右旋”323                             tree = RotateRR(tree);324                         }325 326                         //继续旋转327                         tree = Remove(key, value, tree);328                     }329                     else330                     {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         #endregion344     }345 }

 

 

6天通吃树结构—— 第三天 Treap树