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         #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相比严格的

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

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

  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 }
时间: 2024-10-11 16:18:52

6天通吃树结构—— 第三天 Treap树的相关文章

6天通吃树结构—— 第五天 Trie树

原文:6天通吃树结构-- 第五天 Trie树 很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等. 一:概念 下面我们有and,as,at,cn,com这些关键词,那么如何构建trie树呢? 从上面的图中,我们或多或少的可以发现一些好玩的特性. 第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符. 第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串. 第三:每个单词的公共前缀作为一个字符节点保存. 二:使用范围 既然

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

原文:6天通吃树结构-- 第四天 伸展树 我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种 伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们 的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢. 一:伸展树 1:思想 伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这 棵树中

6天通吃树结构—— 第一天 二叉查找树

原文:6天通吃树结构-- 第一天 二叉查找树 一直很想写一个关于树结构的专题,再一个就是很多初级点的码农会认为树结构无用论,其实归根到底还是不清楚树的实际用途. 一:场景: 1:现状 前几天我的一个大学同学负责的网站出现了严重的性能瓶颈,由于业务是写入和读取都是密集型,如果做缓存,时间间隔也只能在30s左 右,否则就会引起客户纠纷,所以同学也就没有做缓存,通过测试发现慢就慢在数据读取上面,总共需要10s,天啊...原来首页的加载关联 到了4张表,而且表数据中最多的在10w条以上,可以想象4张巨大

6天通吃树结构—— 第二天 平衡二叉树

原文:6天通吃树结构-- 第二天 平衡二叉树 上一篇我们聊过,二叉查找树不是严格的O(logN),导致了在真实场景中没有用武之地,谁也不愿意有O(N)的情况发生, 作为一名码农,肯定会希望能把“范围查找”做到地球人都不能优化的地步. 当有很多数据灌到我的树中时,我肯定会希望最好是以“完全二叉树”的形式展现,这样我才能做到“查找”是严格的O(logN), 比如把这种”树“调正到如下结构. 这里就涉及到了“树节点”的旋转,也是我们今天要聊到的内容. 一:平衡二叉树(AVL) 1:定义 父节点的左子树

[转]使用设计模式改善程序结构(三)

使用设计模式改善程序结构(三) 设计模式在某种程度上确实能够改善我们的程序结构,使设计具有更好的弹性.也正是由于这个原因,会导致我们可能过度的使用它.程序结构具有过度的.不必要的灵活性和程序结构没有灵活性一样都是有害的.本文将分析过度的灵活性可能造成的危害,并且结合一些实例来阐述使用设计模式改善程序结构应遵循的原则. 1. 介绍 本系列文章的前两篇主要讲述了如何使用设计模式来改善我们的程序结构,大家可以看到经过调整的代码具有了更大的弹性,更容易适应变化.读者朋友可能也具有类似的经验,通过使用设计

Treap树

(a) ①根据堆性质以每个结点为根的子树的最小优先级都是在子树的根上.②根据二叉搜索树性质,以每个节点的根的子树的左子树<右子树. 根据这两点根和左右子树都是被唯一确定,因此能够确定整棵树. (b)Treap树是二叉查找树的一种,而二叉查找树期望高度为O(lgn),所以treap期望高度为O(lgn). (c)代码如下: //13-4 Treap树 #include <iostream> #include <time.h> using namespace std; #defi

Treap树的基础知识

原文 其它较好的的介绍:堆排序  AVL树 树堆,在数据结构中也称Treap(事实上在国内OI界常称为Traep,与之同理的还有"Tarjan神犇发明的"Spaly),是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树.其基本操作的期望时间复杂度为O(logn).相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构. 编辑 我们可以看到,如果一个二叉排序树节点插入的顺序是随机的,这样我们得到的二叉排序树大多数情况下是平衡的

Atitit 常见的树形结构 红黑树 &#160;二叉树 &#160;&#160;B树 B+树 &#160;Trie树&#160;attilax理解与总结

Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树2 1.4. 满二叉树和完全二叉树..完全二叉树说明深度达到完全了.2 1.5. 属的逻辑表示 树形比奥死,括号表示,文氏图,凹镜法表示3 1.6. 二叉树是数据结构中一种重要的数据结构,也是树表家族最为基础的结构.3 1.6.1. 3.2 平衡二叉

treap树模板

1 ///treap树模板 2 typedef struct Node ///节点的结构体 3 { 4 Node *l,*r; 5 int val,pri; ///节点的值和优先级 6 int sz; ///节点子树的节点数 7 Node(int x) ///初始化节点 8 { 9 l=r=NULL; 10 val=x; 11 pri=rand(); 12 sz=1; 13 } 14 }Node; 15 Node *root; 16 17 int Tsize(Node *T) ///计算子树的叶