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

2012-07-30 10:01

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

by 一线码农

at 2012-07-30 02:01:00

original http://www.cnblogs.com/huangxincheng/archive/2012/07/30/2614484.html

       

       我们知道,二叉查找树相对来说比较容易形成最坏的链表情况,所以前辈们想尽了各种优化策略,包括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 }

 

 

本文链接