上一篇我们聊过,二叉查找树不是严格的O(logN),导致了在真实场景中没有用武之地,谁也不愿意有O(N)的情况发生,
作为一名码农,肯定会希望能把“范围查找”做到地球人都不能优化的地步。
当有很多数据灌到我的树中时,我肯定会希望最好是以“完全二叉树”的形式展现,这样我才能做到“查找”是严格的O(logN),
比如把这种”树“调正到如下结构。

这里就涉及到了“树节点”的旋转,也是我们今天要聊到的内容。
一:平衡二叉树(AVL)
1:定义
父节点的左子树和右子树的高度之差不能大于1,也就是说不能高过1层,否则该树就失衡了,此时就要旋转节点,在
编码时,我们可以记录当前节点的高度,比如空节点是-1,叶子节点是0,非叶子节点的height往根节点递增,比如在下图
中我们认为树的高度为h=2。

1 #region 平衡二叉树节点
2 /// <summary>
3 /// 平衡二叉树节点
4 /// </summary>
5 /// <typeparam name="K"></typeparam>
6 /// <typeparam name="V"></typeparam>
7 public class AVLNode<K, V>
8 {
9 /// <summary>
10 /// 节点元素
11 /// </summary>
12 public K key;
13
14 /// <summary>
15 /// 增加一个高度信息
16 /// </summary>
17 public int height;
18
19 /// <summary>
20 /// 节点中的附加值
21 /// </summary>
22 public HashSet<V> attach = new HashSet<V>();
23
24 /// <summary>
25 /// 左节点
26 /// </summary>
27 public AVLNode<K, V> left;
28
29 /// <summary>
30 /// 右节点
31 /// </summary>
32 public AVLNode<K, V> right;
33
34 public AVLNode() { }
35
36 public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right)
37 {
38 //KV键值对
39 this.key = key;
40 this.attach.Add(value);
41
42 this.left = left;
43 this.right = right;
44 }
45 }
46 #endregion
2:旋转
节点再怎么失衡都逃不过4种情况,下面我们一一来看一下。
① 左左情况(左子树的左边节点)

我们看到,在向树中追加“节点1”的时候,根据定义我们知道这样会导致了“节点3”失衡,满足“左左情况“,可以这样想,把这
棵树比作齿轮,我们在“节点5”处把齿轮往下拉一个位置,也就变成了后面这样“平衡”的形式,如果用动画解释就最好理解了。
1 #region 第一种:左左旋转(单旋转)
2 /// <summary>
3 /// 第一种:左左旋转(单旋转)
4 /// </summary>
5 /// <param name="node"></param>
6 /// <returns></returns>
7 public AVLNode<K, V> RotateLL(AVLNode<K, V> node)
8 {
9 //top:需要作为顶级节点的元素
10 var top = node.left;
11
12 //先截断当前节点的左孩子
13 node.left = top.right;
14
15 //将当前节点作为temp的右孩子
16 top.right = node;
17
18 //计算当前两个节点的高度
19 node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
20 top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
21
22 return top;
23 }
24 #endregion
② 右右情况(右子树的右边节点)

同样,”节点5“满足”右右情况“,其实我们也看到,这两种情况是一种镜像,当然操作方式也大同小异,我们在”节点1“的地方
将树往下拉一位,最后也就形成了我们希望的平衡效果。
1 #region 第二种:右右旋转(单旋转)
2 /// <summary>
3 /// 第二种:右右旋转(单旋转)
4 /// </summary>
5 /// <param name="node"></param>
6 /// <returns></returns>
7 public AVLNode<K, V> RotateRR(AVLNode<K, V> node)
8 {
9 //top:需要作为顶级节点的元素
10 var top = node.right;
11
12 //先截断当前节点的右孩子
13 node.right = top.left;
14
15 //将当前节点作为temp的右孩子
16 top.left = node;
17
18 //计算当前两个节点的高度
19 node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
20 top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
21
22 return top;
23 }
24 #endregion
③左右情况(左子树的右边节点)

从图中我们可以看到,当我们插入”节点3“时,“节点5”处失衡,注意,找到”失衡点“是非常重要的,当面对”左右情况“时,我们将
失衡点的左子树进行”右右情况旋转”,然后进行”左左情况旋转“,经过这样两次的旋转就OK了,很有意思,对吧。
1 #region 第三种:左右旋转(双旋转)
2 /// <summary>
3 /// 第三种:左右旋转(双旋转)
4 /// </summary>
5 /// <param name="node"></param>
6 /// <returns></returns>
7 public AVLNode<K, V> RotateLR(AVLNode<K, V> node)
8 {
9 //先进行RR旋转
10 node.left = RotateRR(node.left);
11
12 //再进行LL旋转
13 return RotateLL(node);
14 }
15 #endregion
④右左情况(右子树的左边节点)

这种情况和“情景3”也是一种镜像关系,很简单,我们找到了”节点15“是失衡点,然后我们将”节点15“的右子树进行”左左情况旋转“,
然后进行”右右情况旋转“,最终得到了我们满意的平衡。
1 #region 第四种:右左旋转(双旋转)
2 /// <summary>
3 /// 第四种:右左旋转(双旋转)
4 /// </summary>
5 /// <param name="node"></param>
6 /// <returns></returns>
7 public AVLNode<K, V> RotateRL(AVLNode<K, V> node)
8 {
9 //执行左左旋转
10 node.right = RotateLL(node.right);
11
12 //再执行右右旋转
13 return RotateRR(node);
14
15 }
16 #endregion
3:添加
如果我们理解了上面的这几种旋转,那么添加方法简直是轻而易举,出现了哪一种情况调用哪一种方法而已。
1 #region 添加操作
2 /// <summary>
3 /// 添加操作
4 /// </summary>
5 /// <param name="key"></param>
6 /// <param name="value"></param>
7 /// <param name="tree"></param>
8 /// <returns></returns>
9 public AVLNode<K, V> Add(K key, V value, AVLNode<K, V> tree)
10 {
11 if (tree == null)
12 tree = new AVLNode<K, V>(key, value, null, null);
13
14 //左子树
15 if (key.CompareTo(tree.key) < 0)
16 {
17 tree.left = Add(key, value, tree.left);
18
19 //如果说相差等于2就说明这棵树需要旋转了
20 if (Height(tree.left) - Height(tree.right) == 2)
21 {
22 //说明此时是左左旋转
23 if (key.CompareTo(tree.left.key) < 0)
24 {
25 tree = RotateLL(tree);
26 }
27 else
28 {
29 //属于左右旋转
30 tree = RotateLR(tree);
31 }
32 }
33 }
34
35 //右子树
36 if (key.CompareTo(tree.key) > 0)
37 {
38 tree.right = Add(key, value, tree.right);
39
40 if ((Height(tree.right) - Height(tree.left) == 2))
41 {
42 //此时是右右旋转
43 if (key.CompareTo(tree.right.key) > 0)
44 {
45 tree = RotateRR(tree);
46 }
47 else
48 {
49 //属于右左旋转
50 tree = RotateRL(tree);
51 }
52 }
53 }
54
55 //将value追加到附加值中(也可对应重复元素)
56 if (key.CompareTo(tree.key) == 0)
57 tree.attach.Add(value);
58
59 //计算高度
60 tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
61
62 return tree;
63 }
64 #endregion
4:删除
删除方法跟添加方法也类似,当删除一个结点的时候,可能会引起祖先结点的失衡,所以在每次”结点“回退的时候计算结点高度。
1 #region 删除当前树中的节点
2 /// <summary>
3 /// 删除当前树中的节点
4 /// </summary>
5 /// <param name="key"></param>
6 /// <param name="tree"></param>
7 /// <returns></returns>
8 public AVLNode<K, V> Remove(K key, V value, AVLNode<K, V> tree)
9 {
10 if (tree == null)
11 return null;
12
13 //左子树
14 if (key.CompareTo(tree.key) < 0)
15 {
16 tree.left = Remove(key, value, tree.left);
17
18 //如果说相差等于2就说明这棵树需要旋转了
19 if (Height(tree.left) - Height(tree.right) == 2)
20 {
21 //说明此时是左左旋转
22 if (key.CompareTo(tree.left.key) < 0)
23 {
24 tree = RotateLL(tree);
25 }
26 else
27 {
28 //属于左右旋转
29 tree = RotateLR(tree);
30 }
31 }
32 }
33 //右子树
34 if (key.CompareTo(tree.key) > 0)
35 {
36 tree.right = Remove(key, value, tree.right);
37
38 if ((Height(tree.right) - Height(tree.left) == 2))
39 {
40 //此时是右右旋转
41 if (key.CompareTo(tree.right.key) > 0)
42 {
43 tree = RotateRR(tree);
44 }
45 else
46 {
47 //属于右左旋转
48 tree = RotateRL(tree);
49 }
50 }
51 }
52 /*相等的情况*/
53 if (key.CompareTo(tree.key) == 0)
54 {
55 //判断里面的HashSet是否有多值
56 if (tree.attach.Count > 1)
57 {
58 //实现惰性删除
59 tree.attach.Remove(value);
60 }
61 else
62 {
63 //有两个孩子的情况
64 if (tree.left != null && tree.right != null)
65 {
66 //根据平衡二叉树的中顺遍历,需要找到”有子树“的最小节点
67 tree.key = FindMin(tree.right).key;
68
69 //删除右子树的指定元素
70 tree.right = Remove(tree.key, value, tree.right);
71 }
72 else
73 {
74 //自减高度
75 tree = tree.left == null ? tree.right : tree.left;
76
77 //如果删除的是叶子节点直接返回
78 if (tree == null)
79 return null;
80 }
81 }
82 }
83
84 //统计高度
85 tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
86
87 return tree;
88 }
89 #endregion
5: 测试
不像上一篇不能在二叉树中灌有序数据,平衡二叉树就没关系了,我们的需求是检索2012-7-30 4:00:00 到 2012-7-30 5:00:00
的登陆用户的ID,数据量在500w,看看平衡二叉树是如何秒杀对手。

View Code
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.IO;
7 using System.Diagnostics;
8
9 namespace DataStruct
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 AVLTree<int, int> avl = new AVLTree<int, int>();
16
17 Dictionary<DateTime, int> dic = new Dictionary<DateTime, int>();
18
19 AVLTree<DateTime, int> tree = new AVLTree<DateTime, int>();
20
21 //500w
22 for (int i = 1; i < 5000000; i++)
23 {
24 dic.Add(DateTime.Now.AddMinutes(i), i);
25
26 tree.Add(DateTime.Now.AddMinutes(i), i);
27 }
28
29 //检索2012-7-30 4:00:00 到 2012-7-30 5:00:00的登陆人数
30 var min = Convert.ToDateTime("2012/7/30 4:00:00");
31
32 var max = Convert.ToDateTime("2012/7/30 5:00:00");
33
34 var watch = Stopwatch.StartNew();
35
36 var result1 = dic.Keys.Where(i => i >= min && i <= max).Select(i => dic[i]).ToList();
37
38 watch.Stop();
39
40 Console.WriteLine("字典查找耗费时间:{0}ms", watch.ElapsedMilliseconds);
41
42 watch = Stopwatch.StartNew();
43
44 var result2 = tree.SearchRange(min, max);
45
46 watch.Stop();
47
48 Console.WriteLine("平衡二叉树查找耗费时间:{0}ms", watch.ElapsedMilliseconds);
49 }
50 }
51
52 #region 平衡二叉树节点
53 /// <summary>
54 /// 平衡二叉树节点
55 /// </summary>
56 /// <typeparam name="K"></typeparam>
57 /// <typeparam name="V"></typeparam>
58 public class AVLNode<K, V>
59 {
60 /// <summary>
61 /// 节点元素
62 /// </summary>
63 public K key;
64
65 /// <summary>
66 /// 增加一个高度信息
67 /// </summary>
68 public int height;
69
70 /// <summary>
71 /// 节点中的附加值
72 /// </summary>
73 public HashSet<V> attach = new HashSet<V>();
74
75 /// <summary>
76 /// 左节点
77 /// </summary>
78 public AVLNode<K, V> left;
79
80 /// <summary>
81 /// 右节点
82 /// </summary>
83 public AVLNode<K, V> right;
84
85 public AVLNode() { }
86
87 public AVLNode(K key, V value, AVLNode<K, V> left, AVLNode<K, V> right)
88 {
89 //KV键值对
90 this.key = key;
91 this.attach.Add(value);
92
93 this.left = left;
94 this.right = right;
95 }
96 }
97 #endregion
98
99 public class AVLTree<K, V> where K : IComparable
100 {
101 public AVLNode<K, V> node = null;
102
103 #region 添加操作
104 /// <summary>
105 /// 添加操作
106 /// </summary>
107 /// <param name="key"></param>
108 /// <param name="value"></param>
109 public void Add(K key, V value)
110 {
111 node = Add(key, value, node);
112 }
113 #endregion
114
115 #region 添加操作
116 /// <summary>
117 /// 添加操作
118 /// </summary>
119 /// <param name="key"></param>
120 /// <param name="value"></param>
121 /// <param name="tree"></param>
122 /// <returns></returns>
123 public AVLNode<K, V> Add(K key, V value, AVLNode<K, V> tree)
124 {
125 if (tree == null)
126 tree = new AVLNode<K, V>(key, value, null, null);
127
128 //左子树
129 if (key.CompareTo(tree.key) < 0)
130 {
131 tree.left = Add(key, value, tree.left);
132
133 //如果说相差等于2就说明这棵树需要旋转了
134 if (Height(tree.left) - Height(tree.right) == 2)
135 {
136 //说明此时是左左旋转
137 if (key.CompareTo(tree.left.key) < 0)
138 {
139 tree = RotateLL(tree);
140 }
141 else
142 {
143 //属于左右旋转
144 tree = RotateLR(tree);
145 }
146 }
147 }
148
149 //右子树
150 if (key.CompareTo(tree.key) > 0)
151 {
152 tree.right = Add(key, value, tree.right);
153
154 if ((Height(tree.right) - Height(tree.left) == 2))
155 {
156 //此时是右右旋转
157 if (key.CompareTo(tree.right.key) > 0)
158 {
159 tree = RotateRR(tree);
160 }
161 else
162 {
163 //属于右左旋转
164 tree = RotateRL(tree);
165 }
166 }
167 }
168
169 //将value追加到附加值中(也可对应重复元素)
170 if (key.CompareTo(tree.key) == 0)
171 tree.attach.Add(value);
172
173 //计算高度
174 tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
175
176 return tree;
177 }
178 #endregion
179
180 #region 计算当前节点的高度
181 /// <summary>
182 /// 计算当前节点的高度
183 /// </summary>
184 /// <param name="node"></param>
185 /// <returns></returns>
186 public int Height(AVLNode<K, V> node)
187 {
188 return node == null ? -1 : node.height;
189 }
190 #endregion
191
192 #region 第一种:左左旋转(单旋转)
193 /// <summary>
194 /// 第一种:左左旋转(单旋转)
195 /// </summary>
196 /// <param name="node"></param>
197 /// <returns></returns>
198 public AVLNode<K, V> RotateLL(AVLNode<K, V> node)
199 {
200 //top:需要作为顶级节点的元素
201 var top = node.left;
202
203 //先截断当前节点的左孩子
204 node.left = top.right;
205
206 //将当前节点作为temp的右孩子
207 top.right = node;
208
209 //计算当前两个节点的高度
210 node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
211 top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
212
213 return top;
214 }
215 #endregion
216
217 #region 第二种:右右旋转(单旋转)
218 /// <summary>
219 /// 第二种:右右旋转(单旋转)
220 /// </summary>
221 /// <param name="node"></param>
222 /// <returns></returns>
223 public AVLNode<K, V> RotateRR(AVLNode<K, V> node)
224 {
225 //top:需要作为顶级节点的元素
226 var top = node.right;
227
228 //先截断当前节点的右孩子
229 node.right = top.left;
230
231 //将当前节点作为temp的右孩子
232 top.left = node;
233
234 //计算当前两个节点的高度
235 node.height = Math.Max(Height(node.left), Height(node.right)) + 1;
236 top.height = Math.Max(Height(top.left), Height(top.right)) + 1;
237
238 return top;
239 }
240 #endregion
241
242 #region 第三种:左右旋转(双旋转)
243 /// <summary>
244 /// 第三种:左右旋转(双旋转)
245 /// </summary>
246 /// <param name="node"></param>
247 /// <returns></returns>
248 public AVLNode<K, V> RotateLR(AVLNode<K, V> node)
249 {
250 //先进行RR旋转
251 node.left = RotateRR(node.left);
252
253 //再进行LL旋转
254 return RotateLL(node);
255 }
256 #endregion
257
258 #region 第四种:右左旋转(双旋转)
259 /// <summary>
260 /// 第四种:右左旋转(双旋转)
261 /// </summary>
262 /// <param name="node"></param>
263 /// <returns></returns>
264 public AVLNode<K, V> RotateRL(AVLNode<K, V> node)
265 {
266 //执行左左旋转
267 node.right = RotateLL(node.right);
268
269 //再执行右右旋转
270 return RotateRR(node);
271
272 }
273 #endregion
274
275 #region 是否包含指定元素
276 /// <summary>
277 /// 是否包含指定元素
278 /// </summary>
279 /// <param name="key"></param>
280 /// <returns></returns>
281 public bool Contain(K key)
282 {
283 return Contain(key, node);
284 }
285 #endregion
286
287 #region 是否包含指定元素
288 /// <summary>
289 /// 是否包含指定元素
290 /// </summary>
291 /// <param name="key"></param>
292 /// <param name="tree"></param>
293 /// <returns></returns>
294 public bool Contain(K key, AVLNode<K, V> tree)
295 {
296 if (tree == null)
297 return false;
298 //左子树
299 if (key.CompareTo(tree.key) < 0)
300 return Contain(key, tree.left);
301
302 //右子树
303 if (key.CompareTo(tree.key) > 0)
304 return Contain(key, tree.right);
305
306 return true;
307 }
308 #endregion
309
310 #region 树的指定范围查找
311 /// <summary>
312 /// 树的指定范围查找
313 /// </summary>
314 /// <param name="min"></param>
315 /// <param name="max"></param>
316 /// <returns></returns>
317 public HashSet<V> SearchRange(K min, K max)
318 {
319 HashSet<V> hashSet = new HashSet<V>();
320
321 hashSet = SearchRange(min, max, hashSet, node);
322
323 return hashSet;
324 }
325 #endregion
326
327 #region 树的指定范围查找
328 /// <summary>
329 /// 树的指定范围查找
330 /// </summary>
331 /// <param name="range1"></param>
332 /// <param name="range2"></param>
333 /// <param name="tree"></param>
334 /// <returns></returns>
335 public HashSet<V> SearchRange(K min, K max, HashSet<V> hashSet, AVLNode<K, V> tree)
336 {
337 if (tree == null)
338 return hashSet;
339
340 //遍历左子树(寻找下界)
341 if (min.CompareTo(tree.key) < 0)
342 SearchRange(min, max, hashSet, tree.left);
343
344 //当前节点是否在选定范围内
345 if (min.CompareTo(tree.key) <= 0 && max.CompareTo(tree.key) >= 0)
346 {
347 //等于这种情况
348 foreach (var item in tree.attach)
349 hashSet.Add(item);
350 }
351
352 //遍历右子树(两种情况:①:找min的下限 ②:必须在Max范围之内)
353 if (min.CompareTo(tree.key) > 0 || max.CompareTo(tree.key) > 0)
354 SearchRange(min, max, hashSet, tree.right);
355
356 return hashSet;
357 }
358 #endregion
359
360 #region 找到当前树的最小节点
361 /// <summary>
362 /// 找到当前树的最小节点
363 /// </summary>
364 /// <returns></returns>
365 public AVLNode<K, V> FindMin()
366 {
367 return FindMin(node);
368 }
369 #endregion
370
371 #region 找到当前树的最小节点
372 /// <summary>
373 /// 找到当前树的最小节点
374 /// </summary>
375 /// <param name="tree"></param>
376 /// <returns></returns>
377 public AVLNode<K, V> FindMin(AVLNode<K, V> tree)
378 {
379 if (tree == null)
380 return null;
381
382 if (tree.left == null)
383 return tree;
384
385 return FindMin(tree.left);
386 }
387 #endregion
388
389 #region 找到当前树的最大节点
390 /// <summary>
391 /// 找到当前树的最大节点
392 /// </summary>
393 /// <returns></returns>
394 public AVLNode<K, V> FindMax()
395 {
396 return FindMin(node);
397 }
398 #endregion
399
400 #region 找到当前树的最大节点
401 /// <summary>
402 /// 找到当前树的最大节点
403 /// </summary>
404 /// <param name="tree"></param>
405 /// <returns></returns>
406 public AVLNode<K, V> FindMax(AVLNode<K, V> tree)
407 {
408 if (tree == null)
409 return null;
410
411 if (tree.right == null)
412 return tree;
413
414 return FindMax(tree.right);
415 }
416 #endregion
417
418 #region 删除当前树中的节点
419 /// <summary>
420 /// 删除当前树中的节点
421 /// </summary>
422 /// <param name="key"></param>
423 /// <returns></returns>
424 public void Remove(K key, V value)
425 {
426 node = Remove(key, value, node);
427 }
428 #endregion
429
430 #region 删除当前树中的节点
431 /// <summary>
432 /// 删除当前树中的节点
433 /// </summary>
434 /// <param name="key"></param>
435 /// <param name="tree"></param>
436 /// <returns></returns>
437 public AVLNode<K, V> Remove(K key, V value, AVLNode<K, V> tree)
438 {
439 if (tree == null)
440 return null;
441
442 //左子树
443 if (key.CompareTo(tree.key) < 0)
444 {
445 tree.left = Remove(key, value, tree.left);
446
447 //如果说相差等于2就说明这棵树需要旋转了
448 if (Height(tree.left) - Height(tree.right) == 2)
449 {
450 //说明此时是左左旋转
451 if (key.CompareTo(tree.left.key) < 0)
452 {
453 tree = RotateLL(tree);
454 }
455 else
456 {
457 //属于左右旋转
458 tree = RotateLR(tree);
459 }
460 }
461 }
462 //右子树
463 if (key.CompareTo(tree.key) > 0)
464 {
465 tree.right = Remove(key, value, tree.right);
466
467 if ((Height(tree.right) - Height(tree.left) == 2))
468 {
469 //此时是右右旋转
470 if (key.CompareTo(tree.right.key) > 0)
471 {
472 tree = RotateRR(tree);
473 }
474 else
475 {
476 //属于右左旋转
477 tree = RotateRL(tree);
478 }
479 }
480 }
481 /*相等的情况*/
482 if (key.CompareTo(tree.key) == 0)
483 {
484 //判断里面的HashSet是否有多值
485 if (tree.attach.Count > 1)
486 {
487 //实现惰性删除
488 tree.attach.Remove(value);
489 }
490 else
491 {
492 //有两个孩子的情况
493 if (tree.left != null && tree.right != null)
494 {
495 //根据平衡二叉树的中顺遍历,需要找到”有子树“的最小节点
496 tree.key = FindMin(tree.right).key;
497
498 //删除右子树的指定元素
499 tree.right = Remove(tree.key, value, tree.right);
500 }
501 else
502 {
503 //自减高度
504 tree = tree.left == null ? tree.right : tree.left;
505
506 //如果删除的是叶子节点直接返回
507 if (tree == null)
508 return null;
509 }
510 }
511 }
512
513 //统计高度
514 tree.height = Math.Max(Height(tree.left), Height(tree.right)) + 1;
515
516 return tree;
517 }
518 #endregion
519 }
520 }

wow,相差98倍,这个可不是一个级别啊…AVL神器。
本文深入探讨AVL树的原理及实现,包括四种旋转操作、添加与删除节点的方法,并通过实例对比展示AVL树在大规模数据查找上的优势。

191

被折叠的 条评论
为什么被折叠?



