上一篇我们聊过,二叉查找树不是严格的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 else28 {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 else48 {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 else27 {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 else46 {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 else62 {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 else73 {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 : IComparable100 {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 #endregion114 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 else142 {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 else162 {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 #endregion179 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 #endregion191 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 #endregion216 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 #endregion241 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 #endregion257 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 #endregion274 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 #endregion286 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 #endregion309 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 #endregion326 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 #endregion359 360 #region 找到当前树的最小节点361 /// <summary>362 /// 找到当前树的最小节点363 /// </summary>364 /// <returns></returns>365 public AVLNode<K, V> FindMin()366 {367 return FindMin(node);368 }369 #endregion370 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 #endregion388 389 #region 找到当前树的最大节点390 /// <summary>391 /// 找到当前树的最大节点392 /// </summary>393 /// <returns></returns>394 public AVLNode<K, V> FindMax()395 {396 return FindMin(node);397 }398 #endregion399 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 #endregion417 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 #endregion429 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 else456 {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 else475 {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 else491 {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 else502 {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 #endregion519 }520 }
wow,相差98倍,这个可不是一个级别啊...AVL神器。