首页 > 代码库 > 6天通吃树结构—— 第二天 平衡二叉树

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

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

       

      上一篇我们聊过,二叉查找树不是严格的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神器。

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