AVL树的旋转

定义

        一棵AVL树可以是空树,也可以是具有下列性质的二叉搜索树:它的左子树和右子树都是AVL树,且左子树和右子树的高度之差的绝对值不超过1(也就是小于等于1)。(右子树高度 - 左子树高度)

        节点的平衡因子:给每个节点附加一个数字,给出该节点右子树的高度减去左子树的高度所得的高度差,这个数字即为节点的平衡因子。

        如果一棵二叉搜索树的高度是平衡的,它就成为了AVL树,如果它有n个节点,其高度可保持在O(log2n)

AVL树的旋转

        平衡化旋转
                ① 如果在一棵平衡的二叉搜索树中插入一个新节点,造成了不平衡。此时必须调整树的结构,使之平衡化。

                ② 平衡化旋转有两类单旋转和双旋转

                其中单旋转又可分为左单旋转和右单旋转

        每插入一个新节点时,AVL树中相关的平衡状态会发生改变,因此,在插入一个新节点后,需要从插入的位置沿向根的路径回溯,检查各节点的平衡因子

        如果在某一节点发现高度不平衡,停止回溯,从发生不平衡的节点起,沿刚才回溯的路径直接下两层的节点,如果这三个节点处于一条直线上,则采用单旋进行平衡化。单旋转可按其旋转方向分为左单旋转和右单旋转,其中一个是另一个的镜像,其旋转方向与不平衡的形状有关。

        如果这三个节点处于一条折线上,则采用双旋转进行平衡化。双旋转分为先左后右和先右后左两类

左单旋转

        如图,此时这棵树是一根平衡的二叉搜索树,所以我们也可以称这棵树为AVL树。每个节点的平衡因子用红色字体标出。我们可以看到,每个节点的平衡因子的绝对值都没有超过1.此时该树平衡。
在这里插入图片描述

        此时,我们插入新的节点,节点值为85,我们发现,50这个节点的平衡因子超过1了。这棵树已经是不平衡的了,如下图所示:
在这里插入图片描述
        根据我们的旋转规则:从新加入的节点开始沿根的路径回溯,找到不平衡的节点,从这个不平衡的节点开始沿刚才回溯的路径向下寻找两层节点,如果这三个节点在同一直线上,我们就是用单旋转,否则使用双旋转。依据这个规则,我们找到了不平衡的节点50,向下两层找到了节点70、节点80。这三个节点在同一直线上,所以我们采用单旋转。

        以不平衡节点的下一个节点为旋转点,进行左单旋转:如图所示:
在这里插入图片描述
        如上图所示,我们首先定义一个ptr引用指向不平衡的节点,然后它的右孩子,是我们旋转后的根节点,我们将它定义为newRoot

        具体是怎么旋转的,我们来看看下图:
在这里插入图片描述
        具体代码实现:

// AVL树的结构与二叉搜索树的结构相似,只是多了一个 int balance遍历表示平衡因子。
// 这里AVL树的结构就不再给出。直接给出坐旋转的代码。

    public void rotateLeft(ANode ptr){
        ANode newRoot = ptr.rightChild; // ①
        newRoot.parent = ptr.parent; // 维护newRoot的双亲。

        // 维护newRoot.leftChild的双亲。
        if (newRoot.leftChild!=null) {
            newRoot.leftChild.parent = ptr;
        }
        ptr.rightChild = newRoot.leftChild;//③


        newRoot.leftChild = ptr; // ②
        ANode pa = ptr.parent;
        // pa == null 说明旋转前,ptr就是根节点
        if (pa==null){
            root = newRoot;
        }else{
            // pa != null 说明ptr要么是上一棵AVL树的左子树
            // 要么是上一棵AVL树的右子树。
            if (pa.leftChild == ptr){
                pa.leftChild = newRoot;
            }
            if (pa.rightChild == ptr){
                pa.rightChild = newRoot;
            }
        }
        
        //维护ptr的双亲
        ptr.parent = newRoot;
    }

右单旋转

        右单旋转和左单旋转的思路是一样的,只不过是旋转的方向不同,我们还是用图示来说明。

        如图,此时是一棵平衡的AVL树。
在这里插入图片描述

        当我们向这棵AVL树中添加 节点10时,如下图所示:
在这里插入图片描述
        此时,我们从新插入的节点10开始,沿着根路径向上回溯的查找,直到查找到80这个节点,发现该节点的平衡因子的绝对值大于1,于是,从该节点向下,沿着刚才回溯的路径,找到下两层节点40、30。这三个节点在同一条直线上,此时以40这个节点为旋转点,进行右单旋转

        如下图:
在这里插入图片描述
        代码实现:

    // AVL树右单旋转
    public void rotateRight(ANode ptr){
        ANode newRoot = ptr.leftChild;
        newRoot.parent = ptr.parent; //维护newRoot的双亲
        
        //维护newRoot.rightChild的双亲
        if (newRoot.rightChild!=null){
            newRoot.rightChild.parent = ptr;
        }
        ptr.leftChild = newRoot.rightChild;

        newRoot.rightChild = ptr;
        ANode pa = ptr.parent;
        if (pa == null){
            root = newRoot;
        }else{
            if (pa.rightChild == ptr){
                pa.rightChild = newRoot;
            }
            if(pa.leftChild ==ptr){
                pa.leftChild = newRoot;
            }
        }
        ptr.parent = newRoot; //维护ptr的双亲
    }

双旋转

        若某一个节点的平衡因子发生改变,沿着回溯路径,以最后一层的节点为旋转点,进行先左单旋后右单旋,或者进行先右单旋后左单旋。

        现有一棵AVL树,每个节点的平衡因子的绝对值 都没有超过1,所以该AVL是平衡的。
在这里插入图片描述

        此时,我们新节点插入的位置有四种,分别为上述图中标出来的1、2、3、4这四个位置。

        情况一
        首先分析,如果我们插入的位置为1或者2这两个位置其中之一,则有:
在这里插入图片描述

        此时,我们发现无论我们插入的是1位置 还是 2位置,节点B的平衡因子都会变成-1,而节点A的平衡因子都会变成-2。都会导致AVL树不平衡,需要旋转。

        我们从新插入的节点开始,向上回溯至不平衡节点的位置,找到了A节点,于是按照旋转规则:找到刚才回溯路径包括A节点在内的3个节点,即A、B、D开始进行旋转。 即,如果我们新的节点插入到1或者2这两个位置其中之一,我们需要旋转的条件就是:当B的平衡因子变为-1时,我们就要进行单旋转,再进行完单旋转后,我们还需要对B的平衡因子重新赋值。

        我们上面分析过了,无论插入1还是2位置,只是进行单旋转,如下图:
在这里插入图片描述

在这里插入图片描述

        我们发现了一个有意思的事情,无论新插入的节点插入1位置还是2位置,在旋转前,B的平衡因子都会变为-1,而在旋转后,B的平衡因子会变为0,A的平衡因子也会变为0。声明一点:图中没有标注ptrnewRoot两个引用,ptrnewRoot的指向和右单旋转是一样的。

        情况二

        当我们新节点插入的是3位置
在这里插入图片描述
        此时,B的平衡因子变为1,E的平衡因子变为-1。从新插入节点F处开始向上查找不平衡的节点,找到了A,于是按照遍历规则,A及其下两层的节点要进行旋转。由于A、B、E这三个节点不在一条直线上,所以我们需要双旋转。先以E节点为旋转点,EB进行左单旋转。如图所示:

在这里插入图片描述
        接着,再以E为旋转带你,EA为轴进行右单旋转,如图:
在这里插入图片描述
        此时,我们发现,E的平衡因子变为0,A的平衡因子变为1。

        整体旋转过程如图所示:
在这里插入图片描述
        情况三

        当新节点插入的位置在4号位置时,如图所示:此时,B的平衡因子还是变为1,E的平衡因子变为了 1 。
在这里插入图片描述
        于是,我们接着按照旋转规则,从新插入节点F处向上查找不平衡的节点,找到A节点,然后A节点向下两层找到A、B、E三个节点,这三个节点不在一条直线上,我们需要进行双旋转。

        首先,以E为旋转点,EB进行左单旋,如图所示:
在这里插入图片描述
        再以E为旋转点,EA进行右单旋,如图:此时我们发现E的平衡因子为0,A的平衡因子为0,B的平衡因子为-1。
在这里插入图片描述
        整体流程如图所示:
在这里插入图片描述
        至此,我们分析完了所有情况,我们回顾一下,三种不同情况各节点的平衡因子变化情况:

        情况一:新节点插入1位置
在这里插入图片描述

        情况一:新节点插入2位置

在这里插入图片描述
        我们发现,节点A和节点B的平衡因子都变为了0,其余节点的平衡因子都没有发生改变。

        我们再来看看情况二,新节点插入3位置:我们发现,A、B、E三个节点的平衡因子都发生了改变。
在这里插入图片描述
        我们再来看看情况三,新节点插入4位置:我们发现,同样的是A、B、E三个节点的平衡因子发生改变。其余节点的平衡因子没有发生改变。
在这里插入图片描述
        代码实现

    // AVL树的左平衡
    public void leftBalance(ANode ptr){
        ANode leftSub = ptr.leftChild;
        ANode rightSub = null;
        switch(leftSub.balance){
            // 说明新插入节点的位置在1位置或2位置
            case -1:
                leftSub.balance = 0;
                ptr.balance = 0;
                rotateRight(ptr);
                break;
            case 0:
                System.out.println("the tree is balance");
                break;

            // 说明新插入的节点的位置在3位置或4位置,具体情况还要分析
            case 1:
                 rightSub = leftSub.rightChild;
                 switch (rightSub.balance){
                     // 说明插入的是3位置
                     case -1:
                        ptr.balance = 1;
                        leftSub.balance = 0;
                     // 说明插入的是4位置
                     case 1:
                         ptr.balance = 0;
                         leftSub.balance = -1;
                     case 0:
                         ptr.balance = 0;
                         leftSub.balance = 0;
                 }
                 rightSub.balance = 0;
                 rotateLeft(leftSub);
                 rotateRight(ptr);
                 break;
        }
    }

        以上就是我们的双旋转的一种情况:先左单旋转后右单旋转,第二种情况:先右单旋转后左单旋转这里就不再做演示了。和先左后右旋转是一样的思路。如过程中有不对的地方,希望各位能够指点,谢谢!

  • 25
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值