• 数据结构:关于AVL树的平衡旋转详解


    前言

      本文是基于你已经有一定的二叉排序树知识。如果你还是小白,可以参考我之前的博客:《数据结构:二叉搜索树(BST)的基本操作》。所以,在本文中不会再出现关于BST树的基本知识。


    版本说明

    著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    作者:Coding-Naga
    发表日期: 2015年12月28日
    链接:http://blog.csdn.net/lemon_tree12138/article/details/50393548
    来源:CSDN
    更多内容:分类 >> 算法与数学


    概述

      AVL树又叫做平衡二叉树。前言部分我也有说到,AVL树的前提是二叉排序树(BST或叫做二叉搜索树)。由于在生成BST树的过程中可能会出现线型树结构,比如插入的顺序是:1, 2, 3, 4, 5, 6, 7..., n在BST树中,比较理想的状况是每个子树的左子树和右子树的高度相等,此时搜索的时间复杂度是log(N)。可是,一旦这棵树演化成了线型树的时候,这个理想的情况就不存在了,此时搜索的时间复杂度是O(N),在数据量很大的情况下,我们并不愿意看到这样的结果。

      现在我们要做的事就是让BST在创建的过程中不要向线型树发展。方法就是让其在添加新节点的时候,不断调整树的平衡状态。

      定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。


    AVL树实现

    1.类图

      首先我们来看一下本文编写代码的类图(见图-1)。

      因为考虑到图片内容的篇幅问题,类图中有一些地方的表述以eg.或getters(setters)省略掉了,详细内容请以代码为准。

      知道策略模式或是看过我之前的博客《Java设计模式——策略模式》的都知道,这里我使用了策略模式来编写代码,4种不同的旋转方式明显的一个策略模式。这样一来,代码更加简捷,逻辑也更加清晰了。


    图-1 AVL树程序类图


    2.节点失衡

      我们对于节点平衡有这样的定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。而这里提到的高度差,就是我们下面会引入的平衡因子:BF

      因为AVL树说到底还是一个二叉树,只有两个子节点。而且节点失衡的发生,是因为有一个新节点的插入,这个新插入的节点导致了某些节点左右子节点高度的不一致。所以我们可以枚举出以下4种情况的失衡状态。

    (1)在一个节点的左子树的左子树上插入一个新节点。即LL。在这种情况下,我们可以通过将节点右旋使其平衡。如图-2所示;


    图-2 LL单右旋操作

    (2)在一个节点的右子树的右子树上插入一个新节点。即RR。在这种情况下,我们可以通过将节点左旋使其平衡。如图-3所示;


    图-3 RR单左旋操作

    (3)在一个节点的左子树的右子树上插入一个新节点。即LR。在这种情况下,我们不能直接通过将节点左旋或右来使其平衡了。这里需要两步来完成,先让树中高度较低的进行一次左旋,这个时候就变成了LL了。再进行一次单右旋操作即可。如图-4所示;


    图-4 LR先左旋再右旋操作

    (4)在一个节点的右子树的左子树上插入一个新节点。即RL。在这种情况下,我们不能直接通过将节点左旋或右来使其平衡了。这里需要两步来完成,先让树中高度较低的进行一次右旋,这个时候就变成了RR了。再进行一次单左旋操作即可。如图-5所示;

    图-5 RL先右旋再左旋操作

    3.旋转调节平衡

      从上面对节点失衡的说明,以及图解。我想你已经对旋转的操作有了一个大概地认识了吧。从图中我们也可以看出,LL型和RR型、LR型和RL型是两个行为很相似地操作。其实他们互为对称。所以在下面的讲解中,我只针对LL型和LR型两种操作进行详细讲解,另外两种是类似的。

      这里还有一点,可能读者会有一些不是太明白。就是我们在旋转的过程中,比如左旋,到底是怎么旋转法,以哪个节点为中心点旋转?

      上面我们说到,失衡节点的平衡因子的绝对值是大于1的。AVL树失衡类型的判断都基于这个失衡节点的。也就是说LL型需要调整的是失衡节点的左子树的左子树,LR型需要调整的是失衡节点左子树的右子树。是不是有一点绕,看看上面的旋转操作图就应该知道了。那么,对于单旋转操作也就很简单了,就是以中间节点为中心旋转。而双旋转中,因为不是一次旋转,不能在一开始就确定旋转中心点,我们在第一次旋转的过程中,只是旋转后面的两个节点,并保证节点值的大小关系,如图-4所示。后面的问题就转化成了单旋转操作了。

    LL型:

      针对图-2所示的LL型失衡情况,可以看到节点15上是没有右孩子的,这个时候转成平衡之后,不用考虑节点15的右孩子与旋转平衡调节之后的节点20是否有冲突。

      关键代码如下:

    public class AdjustLL implements Adjustable {
    
        @Override
        public void adjust(AVLTree tree, Node imbalanceNode) {
            Node parentNode = imbalanceNode.getParent();
            Node leftNode = imbalanceNode.getLeft(); // 新添加的节点
            
            resetImbalanceNode(imbalanceNode, leftNode);
            resetLeftNode(tree, leftNode, imbalanceNode, parentNode);
            resetParentNode(parentNode, leftNode);
        }
    
        /*
         * 重置失衡节点
         * 
         * @param imbalanceNode
         *      失衡节点
         */
        private void resetImbalanceNode(Node imbalanceNode, Node leftNode) {
            ... ...
        }
        
        /*
         * 重置失衡节点的左节点
         * 
         * @param leftNode
         *      左节点
         * @param imbalanceNode
         *      失衡节点
         * @param parentNode
         *      父节点
         */
        private void resetLeftNode(AVLTree tree, Node leftNode, Node imbalanceNode, Node parentNode) {
            ... ...
        }
        
        /*
         * 重置父节点
         * 
         * @param parentNode
         *      父节点
         * @param leftNode
         *      左节点
         */
        private void resetParentNode(Node parentNode, Node leftNode) {
            ... ...
        }
    }

    RR型:

      类似LL型,详细代码请参见下面的GitHub源码。

    RL型:

      对于RL型的就比较麻烦一些了,比如我们现在插入的序列是这样的:{20, 15, 30, 40, 25, 23}。那么在调整平衡之前,我们的树结构是这样的(如图-6所示):


    图-6 RL型失衡

      这时我们就不能直接进行一次左旋或是右旋就可以搞定的了。虽然我们不能一步直接达到我们的要求,但是分两步就可以了呀。操作图示参见图-7.


    图-7 RL型失衡的旋转过程

      第一次右旋的关键代码:

    /*
         * 第一次右旋
         * (此处方法调用的顺序不要修改)
         * 
         * @param tree
         *      AVL树
         * @param imbalanceNode
         *      失衡节点
         * @param rightChildNode
         *      失衡节点的右孩子
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void firstRightAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {
            firstRightChildAdjust(rightChildNode, rightLeftNode);
            firstRightLeftAdjust(imbalanceNode, rightChildNode, rightLeftNode);
            firstImbalanceAdjust(imbalanceNode, rightLeftNode);
        }
        
        /*
         * 调整失衡节点
         * 
         * @param imbalanceNode
         *      失衡节点
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void firstImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) {
            imbalanceNode.setRight(rightLeftNode);
            
            imbalanceNode.resetHeight();
            imbalanceNode.resetBF();
        }
        
        /*
         * 调整失衡节点的右孩子
         * 
         * @param rightChildNode
         *      失衡节点的右孩子
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void firstRightChildAdjust(Node rightChildNode, Node rightLeftNode) {
            rightChildNode.setParent(rightLeftNode);
            rightChildNode.setLeft(null);
            
            rightChildNode.resetHeight();
            rightChildNode.resetBF();
        }
        
        /*
         * 调整失衡节点右孩子的左孩子
         * 
         * @param imbalanceNode
         *      失衡节点
         * @param rightChildNode
         *      失衡节点的右孩子
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void firstRightLeftAdjust(Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {
            rightLeftNode.setRight(rightChildNode);
            rightLeftNode.setParent(imbalanceNode);
            
            rightLeftNode.resetHeight();
            rightLeftNode.resetBF();
        }
      第二次左旋的关键代码:

    /*
         * 第二次左旋
         * 
         * @param tree
         *      AVL树
         * @param imbalanceNode
         *      失衡节点
         * @param rightChildNode
         *      失衡节点的右孩子
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void secondLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightChildNode, Node rightLeftNode) {
            Node parentNode = imbalanceNode.getParent();
            
            secondRightChildAdjust(rightChildNode);
            secondImbalanceAdjust(imbalanceNode, rightLeftNode);
            secondRightLeftAdjust(tree, imbalanceNode, rightLeftNode, parentNode);
        }
        
        /*
         * 调整失衡节点
         * 
         * @param imbalanceNode
         *      失衡节点
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         */
        private void secondImbalanceAdjust(Node imbalanceNode, Node rightLeftNode) {
            if (rightLeftNode.getLeft() != null) {
                imbalanceNode.setRight(rightLeftNode.getLeft());
            }
            
            imbalanceNode.setParent(rightLeftNode);
            
            imbalanceNode.resetHeight();
            imbalanceNode.resetBF();
        }
        
        /*
         * 调整失衡节点的右孩子
         * 
         * @param rightChildNode
         *      失衡节点的右孩子
         */
        private void secondRightChildAdjust(Node rightChildNode) {
            rightChildNode.resetHeight();
            rightChildNode.resetBF();
        }
        
        /*
         * 调整失衡节点右孩子的左孩子
         * 
         * @param tree
         *      AVL树
         * @param imbalanceNode
         *      失衡节点
         * @param rightLeftNode
         *      失衡节点右孩子的左孩子
         * @param parentNode
         *      失衡节点的父节点
         */
        private void secondRightLeftAdjust(AVLTree tree, Node imbalanceNode, Node rightLeftNode, Node parentNode) {
            rightLeftNode.setParent(parentNode);
            if (parentNode == null) {
                tree.resetRoot(rightLeftNode);
            }
            
            rightLeftNode.setLeft(imbalanceNode);
            
            rightLeftNode.resetHeight();
            rightLeftNode.resetBF();
        }

    LR型:

      类似RL型,详细代码请参见下面的GitHub源码。


    Ref

    • 数据结构(C语言版)--清华大学出版社
    • 大话数据结构--清华大学出版社


    GitHub源码下载

    https://github.com/William-Hai/Balanced-Binary-Tree

  • 相关阅读:
    HEOI2016 题解
    数据结构考前复习【已弃】
    BZOJ 3993: [SDOI2015]星际战争 [二分答案 二分图]
    BZOJ 4698: Sdoi2008 Sandy的卡片 [后缀自动机]
    BZOJ 3990: [SDOI2015]排序 [搜索]
    字符串考前总结
    BZOJ 3881: [Coci2015]Divljak [AC自动机 树链的并]
    CF698C. LRU [容斥原理 概率]
    BZOJ 2707: [SDOI2012]走迷宫 [高斯消元 scc缩点]
    BZOJ 1444: [Jsoi2009]有趣的游戏 [AC自动机 高斯消元]
  • 原文地址:https://www.cnblogs.com/fengju/p/6336022.html
Copyright © 2020-2023  润新知