zoukankan      html  css  js  c++  java
  • AVL 树的插入、删除、旋转归纳

    参考链接:
     
    1126号注:先前有一个概念搞混了:
      节点的深度 Depth 是指从根节点到当前节点的长度;
      节点的高度 Height 是指从当前节点向下,到子孙中所有叶子节点的长度的最大值。
     
     
    之前简单了解过 AVL 树,知道概念但一直没动手实践过。Now
     
     AVL 树是二叉搜索树的一种。二叉搜索树的规则就是:每个节点的 left child 都比自己小,right child 都比自己大。而 AVL 的在此之上又加了一个规则:每个节点的 left 深度与 right 深度只差<=1,这样就能充分利用 二叉树的结构,避免出现一个分支走到黑,导致搜索效率变低。如下图:
                                                 
                                    这种树结构,搜索起来完全没有体现出二叉搜索树的优势
     
    所以同样多的元素,搜索树的深度越低,就会越快的定位到搜索点。
     
    AVL 树为了保持树的平衡,每次插入、删除都会检查 每个受影响的节点,深度差是否 <  1。最难的部分就是如果树失衡,如何调整节点。插入的处理流程大致如下:
         给定一个 value,一路比较直到插入到某个位置。然后查找哪些节点因此而失衡,针对第一个失衡的节点进行旋转。
     
    下面是我从另一个博客上找来的图(http://blog.csdn.net/gabriel1026/article/details/6311339),介绍了四种失衡的情况,以及调整方式:
    注:图中红色的小块 表示新插入的元素,A 点表示从插入点向上,发现的第一个失衡的点。
     
          AVL树的旋转一共有四种情形,注意所有旋转情况都是围绕着使得二叉树不平衡的第一个节点展开的。
    1: 
         
         图中在 Bl 下新插入一个子节点,导致 A 左侧深度为 3,右侧为 1,失衡。右旋。
    2:
         
    3:
         
     
    4:
     
    不知道大家看明白了调整的规则吗?
    如果您看明白了,你真是大牛!!!
    没看图之前我大致明白了四种调整规则,看了后两幅图片之后,彻底迷糊了。完全不知道如何用程序来表示四种规则,也不知道怎么从一棵树中提取到这四个特征。。。后两张图折磨了我一天多,才发现:后两张图应该是存在逻辑错误的。。。
         AVL 树每次插入之前,以及插入之后,都应该是平衡的。而 图3 和图4,插入之前就不平衡:拿图三来说:插入之前,A 的 left 深度为3,right 深度为 1,本身就有问题
     
     
    虽然上面的图有问题,但是介绍的思路是非常正确的,只是检查到底符合 LL、RR、LR、RL 中哪个特征时,被带跑偏了。
    所以明白了这四种调整方式后,下一步就是用程序的思维,检查何时用哪种规则了。这一点我认为网上的博客介绍的也不太详细,我这理解力一般的人,看过教程后,实现起来依旧是一团麻。下面是我用到的插入流程:
         1:拿到新节点 newNode,根据左小右大的二叉搜索树规则,一路定位到一个具体的节点 currNode,把 newNode 当成 currNode 的 child 插入到树中。(不用记录 newNode 是 currNode 的 left 还是 right )
         2:检查 currNode 是否失衡,如果没有失衡,就检查 currNode 的 parent,不失衡就在检查 parent 的 parent….. 这样直到拿到一个失衡节点(如果直到根节点依旧平衡,那当前树就不用做任何调整),把失衡点当成 currNode
         3:关键点的平衡来了:
              如果 Depth(currNode.left) - Depth(currNode.right) = 2:说明左侧深度大于右侧深度,可能是 LL、LR 中的一种:此时检查 newNode 和 currNode、currNode.left 的大小关系
                   1)如果 newNode 比 currNode 小,并且 newNode 比 currNode.left ,此时符合 LL:对 currNode 右旋;
                   2)如果 newNode 比 currNode 小,但是 newNode 比 currNode.left ,此时符合 LR:先对 currNode.left 左旋,然后对 currNode 右旋;
              如果 Depth(currNode.right) - Depth(currNode.left) = 2:说明右侧深度大于左侧深度,可能是 RR、RL 中的一种:此时检查 newNode 和 currNode、currNode.right 的大小关系:
                   1)如果 newNode 比 currNode 大,并且 newNode 比 currNode.right 大,符合 RR,对 currNode 左旋;
                   2)如果 newNode 比 currNode 大,并且 newNode 比 currNode.right 小,符合 RL:先对 currNode.right 右旋,然后对 currNode 左旋;
    插入、调整结束。
     
    中间有几个容易混的地方。这样叙述起来,用程序实现就很方便了。剩下的就是 左旋、右旋的函数实现,这两个函数需要特别小心,每个节点的 parent、left、right 都要很小心的处理。基本上包含四个函数:
         insert 负责执行 1
         balance 负责执行 2、3、4
         leftRotate、rightRotate 函数用来实现旋转。
    代码我在最后统一贴出来。
     
     
     
    插入搞定了,还有删除操作:删除的流程如下:
         1:拿到要删除的数字 value,从根节点开始比对,知道找到一个要删除的节点:currNode (没找到正好不用删了 →_→)
         2:从左子树中找到一个最大值(左子树中的值都比 currNode 小):targetNode(如果左子树为空,那就直接把 right 节点上位;如果 right 也是空的,那就直接删掉 currNode 就好了)
         3:把 targetNode 放到 currNode 的位置上:因为每个 节点都有 parent、left、right 三个关联点,要仔细处理(错误了好几次才正确→_→)
         4:和 插入类似,从 targetNode 开始一路向上,找到第一个失衡点。此时只有 LL 和 RR 两种失衡情况,判断起来相对容易些
     
     
    AVL 树最基本的插入和删除就是这样了。插入删除过程中,具体为什么旋转、为什么使用这种规则,是否覆盖到了所有的特例等问题,都是有规律可以归纳的。对这些规律我还比较模糊,知其然不知其所以然。。。树是一个很神奇的数据结构,诸多奇思妙想都能用树结构来实现,还要多想想树的规律。
     
    JS 代码如下:
     
     
      1 /**
      2  * Created by CG on 16/11/20.
      3  */
      4 
      5 
      6 
      7 var TreeNode = function(){
      8     this.parent = null;
      9     this.left = null;
     10     this.right = null;
     11 
     12     this.value = null;
     13 };
     14 
     15 
     16 var AVLTree = {
     17 
     18     insert : function (value) {
     19         this.log("新加节点:new add: " + value);
     20         if(this._tree == null){
     21             var node = new TreeNode();
     22             node.value = value;
     23             this._tree = node;
     24             return;
     25         }
     26 
     27         var newNode = new TreeNode();
     28         newNode.value = value;
     29 
     30         var currNode = this._tree;
     31         while(true){
     32             if(currNode == null){
     33                 this.log(" ======== currNode: null");
     34                 return;
     35             }
     36 
     37             //走向左子树
     38             if(value <= currNode.value){
     39                 this.log(" to left:   value: " + value + " currValue: " + currNode.value);
     40                 if(currNode.left){
     41                     currNode = currNode.left;
     42                     continue;
     43                 }
     44                 else {
     45                     newNode.parent = currNode;
     46                     currNode.left = newNode;
     47                     this.balanceTree(currNode, newNode);
     48                     break;
     49                 }
     50             }
     51             //走向右子树
     52             else {
     53                 this.log(" to right:   value: " + value + " currValue: " + currNode.value);
     54                 if(currNode.right){
     55                     currNode = currNode.right;
     56                     continue;
     57                 }
     58                 else {
     59                     newNode.parent = currNode;
     60                     currNode.right = newNode;
     61                     this.balanceTree(currNode, newNode);
     62                     break;
     63                 }
     64             }
     65         }
     66     },
     67     balanceTree : function (currNode, newNode) {
     68         if(!currNode){
     69             return;
     70         }
     71 
     72         this.printTreeByLevel();
     73         while(currNode){
     74             this.log("---------===========--- check if adjust: " + currNode.value);
     75             if(currNode.parent){
     76                 this.log(" parent: " + currNode.parent.value);
     77             }
     78             var leftDepth   = this.calcuDepth(currNode.left);
     79             var rightDepth  = this.calcuDepth(currNode.right);
     80             this.log("leftDepth: " + leftDepth + "  rightDepth: " + rightDepth);
     81             if(leftDepth - rightDepth == 2){
     82                 if(newNode == null){
     83                     this.rightRotate(currNode);
     84                 }
     85                 else if(newNode.value < currNode.value && newNode.value < currNode.left.value){
     86                     this.log("LL");
     87                     this.rightRotate(currNode);
     88                 }
     89                 else if(newNode.value < currNode.value && newNode.value > currNode.left.value){
     90                     this.log("LR");
     91                     this.leftRotate(currNode.left);
     92                     this.rightRotate(currNode);
     93                 }
     94             }
     95             else if(rightDepth - leftDepth == 2){
     96                 if(newNode == null){
     97                     this.leftRotate(currNode);
     98                 }
     99                 else if(newNode.value > currNode.value && newNode.value > currNode.right.value){
    100                     this.log("RR");
    101                     this.leftRotate(currNode);
    102                 }
    103                 else if(newNode.value > currNode.value && newNode.value < currNode.right.value){
    104                     this.log("RL");
    105                     this.rightRotate(currNode.right);
    106                     this.leftRotate(currNode);
    107                 }
    108             }
    109 
    110             currNode = currNode.parent;
    111             this.printTreeByLevel();
    112         }
    113     },
    114     leftRotate : function (currNode) {
    115         this.log("leftRotate: " + currNode.value);
    116         var oldRight = currNode.right;
    117 
    118         //如果当前节点就是根节点,更新外界引用的根节点
    119         if(currNode == this._tree){
    120             this._tree = oldRight;
    121         }
    122         else {
    123             //更新变动前的 currNode 的 parent 的指向
    124             if(currNode.parent.left == currNode){
    125                 currNode.parent.left = oldRight;
    126             }
    127             else if(currNode.parent.right == currNode){
    128                 currNode.parent.right = oldRight;
    129             }
    130         }
    131 
    132         //更新 curr 和 oldRight 的 parent
    133         oldRight.parent = currNode.parent;
    134 
    135         //更新 curr 和 oldRight 的 child
    136         currNode.right = oldRight.left;
    137         if(oldRight.left){
    138             oldRight.left.parent = currNode;
    139         }
    140 
    141         oldRight.left = currNode;
    142         currNode.parent = oldRight;
    143 
    144         this._tree.parent = null;
    145         return oldRight;
    146     },
    147     rightRotate : function (currNode) {
    148         this.log("rightRotate: " + currNode.value);
    149         var oldLeft = currNode.left;
    150 
    151         //如果当前节点就是根节点,更新外界引用的根节点
    152         if(currNode == this._tree){
    153             this._tree = oldLeft;
    154         }
    155         else {
    156             //更新变动前的 currNode 的 parent 的指向
    157             if(currNode.parent.left == currNode){
    158                 currNode.parent.left = oldLeft;
    159             }
    160             else if(currNode.parent.right == currNode){
    161                 currNode.parent.right = oldLeft;
    162             }
    163         }
    164 
    165         //更新 curr 和 oldLeft 的 parent
    166         oldLeft.parent = currNode.parent;
    167 
    168         //更新 curr 和 oldLeft 的 child
    169         currNode.left = oldLeft.right;
    170         if(oldLeft.right){
    171             oldLeft.right.parent = currNode;
    172         }
    173 
    174         oldLeft.right = currNode;
    175         currNode.parent = oldLeft;
    176 
    177         this._tree.parent = null;
    178         return oldLeft;
    179     },
    180 
    181     /**
    182      * 计算左右节点的深度。叶子节点的深度都是 1,依次向上加 1
    183      * @param treeNode
    184      * @returns {number}
    185      */
    186     calcuDepth : function (treeNode) {
    187         if(!treeNode){
    188             return 0;
    189         }
    190         if(treeNode.left == null && treeNode.right == null){
    191             return 1;
    192         }
    193         return 1 + Math.max(this.calcuDepth(treeNode.left), this.calcuDepth(treeNode.right));
    194     },
    195 
    196     /**
    197      * 从树中删除一个节点
    198      * @param value
    199      */
    200     remove : function (value) {
    201         this.log(" ===== 将要删除元素:" + value);
    202         if(!value){
    203             return;
    204         }
    205 
    206         //定位到节点
    207         var currNode = this._tree;
    208         while(currNode){
    209             if(currNode.value == value){
    210                 break;
    211             }
    212             currNode = value > currNode.value ? currNode.right : currNode.left;
    213         }
    214         if(currNode.value != value){
    215             this.log("没找到啊");
    216             return;
    217         }
    218 
    219         var targetNode = null;
    220         //删除该节点
    221         if(currNode.left){
    222             //有左子树,找到其中最大值来替代空位
    223             targetNode = this.findMaxNode(currNode.left);
    224             this.log(" == currNode.left: " + targetNode.value);
    225 
    226             //更新 target 父节点的 child 指向
    227             if(targetNode.parent != currNode){
    228                 var newChild = targetNode.left ? targetNode.left : targetNode.right;
    229                 if(targetNode.parent.left == targetNode){
    230                     targetNode.parent.left = newChild;
    231                 }
    232                 else {
    233                     targetNode.parent.right = newChild;
    234                 }
    235             }
    236             //更新 target 的 parent 指向
    237             targetNode.parent = currNode.parent;
    238 
    239             // 更新 target 的 right 指向
    240             targetNode.right = currNode.right;
    241             if(currNode.right){
    242                 currNode.right.parent = targetNode;
    243             }
    244             // 更新 target 的 left 指向 、、一定要注意避免自身死循环
    245             if(currNode.left != targetNode){
    246                 targetNode.left = currNode.left;
    247                 currNode.left.parent = targetNode;
    248             }
    249         }
    250         //没有左子树,但是有右子树,直接把右子树提上去就好了
    251         else if(currNode.right){
    252             targetNode = currNode.right;
    253             targetNode.parent = currNode.parent;
    254             this.log(" == currNode.right: " + targetNode.value);
    255         }
    256         //如果 curr 是叶子节点,只要更新 curr 的 parent 就可以了,没有额外处理
    257 
    258         //更新 curr 父节点的 child 指向
    259         if(currNode.parent && currNode.parent.left == currNode){
    260             currNode.parent.left = targetNode;
    261         }
    262         else if(currNode.parent && currNode.parent.right == currNode){
    263             currNode.parent.right = targetNode;
    264         }
    265         else {
    266             this._tree = targetNode; //说明是 根节点
    267         }
    268 
    269         this.log(" +++++++++++++ ");
    270         this.printTreeByLevel();
    271         this.balanceTree(targetNode == null ? currNode.parent : targetNode);
    272         this.log(" +++++++++++++ ");
    273     },
    274 
    275 
    276     findMaxNode : function(treeNode){
    277         while(treeNode){
    278             if(treeNode.right){
    279                 treeNode = treeNode.right;
    280             }
    281             else {
    282                 return treeNode;
    283             }
    284         }
    285         return treeNode;
    286     },
    287 
    288 
    289 
    290     log : function (str) {
    291         console.log(str);
    292     },
    293     /**
    294     * 按照层级打印一棵树的各层节点名字
    295     **/
    296     printTreeByLevel : function () {
    297         this.log("-----------------------");
    298         if(!this._tree){
    299             this.log(" === empty ===");
    300             return;
    301         }
    302         var nodeList = [];
    303         nodeList.push(this._tree);
    304         while(nodeList.length > 0){
    305             var len = nodeList.length;
    306             var value = "";
    307             for(var i=0; i<len; ++i){
    308                 var currNode = nodeList[i];
    309                 value += currNode.value + " ";
    310                 if(currNode.left){
    311                     nodeList.push(currNode.left);
    312                 }
    313                 if(currNode.right){
    314                     nodeList.push(currNode.right);
    315                 }
    316             }
    317             this.log(value);
    318 
    319             nodeList = nodeList.slice(len);
    320         }
    321     },
    322 };
    323 
    324 
    325 AVLTree.printTreeByLevel();
    326 AVLTree.log("====================================================================================================");
    327 var list = [3,7,9,23,45, 1,5,14,25,24, 13,11, 26];
    328 for(var index in list){
    329     AVLTree.insert(list[index]);
    330 }
    331 AVLTree.log("====================================================================================================");
    332 AVLTree.printTreeByLevel();
    333 // AVLTree.remove(1);
    334 // AVLTree.remove(25);
    335 // AVLTree.printTreeByLevel();
    View Code
     
  • 相关阅读:
    基于域名的虚拟主机
    用户认证
    部署lnmp
    django开发流程
    sed 和awk的执行方式
    将文本行倒序排列
    《深入理解JAVA虚拟机》----------第二章 JAVA内存区域与内存溢出异常,笔记(上)
    《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,笔记(下)
    《深入理解JAVA虚拟机》----------第三章 垃圾收集器与内存分配策略,笔记(上)
    洛谷P3783 [SDOI2017]天才黑客(前后缀优化建图+虚树+最短路)
  • 原文地址:https://www.cnblogs.com/cg-wang/p/6096123.html
Copyright © 2011-2022 走看看