数据结构:关于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种不同的旋转方式明显的一个策略模式。这样一来,代码更加简捷,逻辑也更加清晰了。

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

图-1 AVL树程序类图


2.节点失衡

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

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

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

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

图-2 LL单右旋操作

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

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

图-3 RR单左旋操作

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

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

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

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

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

图-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所示):

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

图-6 RL型失衡

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

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

图-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源码下载

《数据结构:关于AVL树的平衡旋转详解》https://github.com/William-Hai/Balanced-Binary-Tree

    原文作者:AVL树
    原文地址: https://blog.csdn.net/lemon_tree12138/article/details/50393548
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞