DataStructure篇:AVL树(平衡二叉树)

平衡二叉树

一、定义

为了避免因二叉搜索树带来树的高度增长过快,效率降低问题,所以就提出了平衡二叉树这个概念,也称为AVL树。
平衡二叉树规定,任意节点的左右子树高度差不能大于1
下面举两个例子说明一下哪个是平衡二叉树。

在这里插入图片描述
从图中可以看出,图一树任何节点的左右子树高度差都不超过1,所以图一属于平衡二叉树。
从图二可以看出,以8为节点的左子树高度为3,右子树高度为2,高度差超过1,所以它不是平衡二叉树。

二、 研究

经过上面的讲解后,大家应该都知道了什么是平衡二叉树了,那么现在就正式开始研究平衡二叉树里面的一些操作与性质。
在开始研究之前,先把需要的数据结构写好,这里我使用的Java语言来写代码。
2.1、节点类定义
// TODO 平衡二叉树节点类
@SuppressWarnings("all")
class AVLNode<T extends Comparable> implements Serializable ,Comparable{
    T value;                 // TODO 该节点的值
    int height;              // TODO 以该节点为根的树的树高
    AVLNode<T> left;         // TODO 该节点的左子树
    AVLNode<T> right;        // TODO 该节点的右子树   

    public AVLNode(T value, int height, AVLNode<T> left, AVLNode<T> right) {
        this.value = value;
        this.height = height;
        this.left = left;
        this.right = right;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setLeft(AVLNode<T> left) {
        this.left = left;
    }

    public void setRight(AVLNode<T> right) {
        this.right = right;
    }

    // TODO 重写compareTo方法是为了方便对节点内的值进行比较,因为节点的值是引用对象,
    // 而不是普通对象,不能直接比较
    @Override
    public int compareTo(Object o) {
        if(this.value != null && o!=null && o instanceof AVLNode){
            AVLNode<T> node = (AVLNode)o;
            if(node.value.compareTo(this.value) == 0) return 0;
            return node.value.compareTo(node.value)>0?1:-1;
        }
        throw new ClassCastException("比较对象的所属类不匹配!");
    }
}

AVL树的数据结构

public final class MyAVLTree<T extends Comparable> implements Serializable {
    private AVLNode<T> root = null;							   // TODO 二叉树根节点
    private AtomicInteger nodeCount = new AtomicInteger(0);    // TODO 记录节点个数

    public MyAVLTree(){
        this(null);
    }

    public MyAVLTree(T rootValue){
        if(rootValue != null){
            root = new AVLNode<>(rootValue,0,null,null);
            nodeCount.incrementAndGet();
        }
    }
}
2.2、判断二叉树是否平衡

这个探讨很重要,因为我们知道,每次我们插入一个节点到二叉树当中,都有可能破坏原二叉树的平衡,所以,怎么判断一个二叉树是否是平衡二叉树就显得十分重要了。

回归定义,二叉树是否平衡的条件就是任意节点的左右子树高度差是否不大于1。所以我们可以从二叉树的高度这块切入这个问题

先写一个获取 以某节点为树的高度 这个函数

// TODO 辅助函数,计算以该节点为树的高度,空树的高度为0
public int getDepth(AVLNode<T> nowNode){
    if(nowNode == null){
        return 0;
    }
    return nowNode.height;
}

现在有了这个函数以后,再来完成判断一棵二叉树是否是平衡二叉树。

// TODO 以nowNode为根节点的树是否是一个平衡二叉树
public boolean checkIsBalanced(AVLNode<T> nowNode){
    if(nowNode == null){
        return true;
    }
    return checkIsBalanced(nowNode.left) && checkIsBalanced(nowNode.right) 
    	&& (Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right))<= 1);
    }

至此,判断平衡二叉树就完成了。

2.3、二叉树的如何保持平衡

下面先研究一下不平衡的二叉树怎么变成平衡二叉树

平衡二叉树的平衡维护分为四种情况:

2.3.1、第一种情况:
在这里插入图片描述
我希望将一号节点插入到右边的平衡二叉树当中,插入的过程如下所示:

在这里插入图片描述
插入过后,我们发现,现在的二叉树是不平衡的,也就是原平衡被打破了,不难看出,最小不平衡节点是节点8,没有比8更小的不平衡节点了(也就说8节点的左右子树都是平衡树

那怎么解决这个不平衡问题,那么就要引出第一种情况不平衡的解决方法了:

假设节点A 是最小不平衡节点,新插入的节点B 在节点A 的左子树的左子树上,那么,可以通过对节点A的左子树节点 进行右旋来重新维持平衡!

概念不好理解,看一个下面的例子:(好好理解一下什么叫节点旋转!

在这里插入图片描述

第一种情况的代码如下:

// TODO 右旋一次, 返回该子树经调整后新的根节点
// TODO 导致失去平衡的节点在最小平衡树节点的左子树的左子树上,就使用LL旋转
/**
  *      6                       4
 *      / \                     / \
 *     4  10     右旋           3  6
 *    / \       ---->         /  / \
 *   3  5                    1  5  10
 *  /
 * 1
*/
// TODO minImbalancedNode 是最小不平衡节点, 返回该不平衡节点所属树的新根节点,
// TODO 根据代码注释上面的例子,原来的6是不平衡节点,现在维护后新的平衡根节点是4,所以返回节点4
public AVLNode<T> toLL(AVLNode<T> minImbalancedNode){
    AVLNode im_left = minImbalancedNode.left;
    AVLNode pre_right = im_left.right;
    im_left.right = minImbalancedNode;
    minImbalancedNode.left = pre_right;
    return im_left;
}

2.3.2、第二种情况:

第二种情况其实跟第一种情况是对称,规则如下:
假设节点A 是最小不平衡节点,新插入的节点B 在节点A 的右子树的右子树上,那么,可以通过对节点A的右子树节点 进行左旋旋来重新维持平衡!

下面举一个例子:(节点12是新插入的

在这里插入图片描述

以上就是第二种情况演示的例子,很好理解,下面贴出代码。

// TODO 导致失去平衡的节点在最小失衡树节点的右子树的右子树上,左旋一次
public AVLNode<T> toRR(AVLNode<T> minImbalancedNode){
    AVLNode im_right = minImbalancedNode.right;
    AVLNode pre_left = im_right.left;
    im_right.left = minImbalancedNode;
    minImbalancedNode.right = pre_left;
    return im_right;
}

2.3.3、第三种情况:

第三种情况可能较前面两种就有点不太一样了,一二两种情况都只是旋转一次即可,第三种开始则需要操作多次了(实际上最大操作次数也就是2次而已

插入节点在最小不平衡节点右子树的左子树上面,称为RL问题,可以先对原不平衡节点的右子树节点先进行右旋,然后再对原不平衡节点位置的右子树节点进行左旋完成平衡操作

我们来看以下这个二叉树。

在这里插入图片描述
我们发现一开始二叉树是平衡的,但是随着12的插入,该二叉树的平衡被打破了,而且最小不平衡点是节点9,因为节点9的左子树和右子树都是平衡二叉树,而且节点9的左子树高度为1,右子树高度为3,高度差为2,这不符合平衡二叉树的规则,所以我们需要对该二叉树进行调整。

进一步来看,节点12位于节点9的右子树的左子树上面,这不符合上面LL与RR的任何一种情况,所以我们不能用一次旋转来完成这个问题。

那我们能不能先通过一次旋转把这个问题转变为上面两种情况的某一种呢?答案是肯定可以的,而且最多只需要旋转一次就可以做到。

而且不能对节点15进行左旋,因为对其进行左旋后,其实只是做了一个对称过程,而且得到的二叉树第一不平衡,第二不符合上述LL和RR任何一种情况,那么这次旋转就显得毫无意义。

我们尝试一下对节点15进行右旋,发现其得到了如下二叉树

在这里插入图片描述
我们发现,虽然该二叉树仍然不平衡,但是现在新插入的节点12来到了原节点位置(也就是节点9)的右子树的右子树上面,所以我们可以采用RR方法来对节点9的右子树节点11进行左旋操作

在这里插入图片描述

注意:可能有的同学会说,经过对节点15的右旋后,最小不平衡树的节点不是11吗,为什么还是9呢,其实这里可以这样子理解,我们这里所处理的对象是原最小不平衡节点的位置,也就是9节点那个位置,而不是在操作完一次后又观察进行的最小不平衡树,你会发现你这样子观察,然后再对15节点做xxxx操作,这是毫无意义的,因为这样子二叉树永远也不会平衡!

最后,第三种情况的代码如下:

// TODO 失去平衡的节点在最小失衡树节点的右子树的左子树上
public AVLNode<T> toRL(AVLNode<T> minImbalancedNode){
    // TODO 先右旋
    this.toLL(minImbalancedNode.right);
    // TODO 再左旋
    return this.toRR(minImbalancedNode.right);
}

2.3.4、第四种情况:

同样的,第四种情况与第三种情况的对称的。

插入节点在最小不平衡节点左子树的右子树上面,称为LR问题,可以先对原不平衡节点的左子树节点先进行左旋,然后再对原不平衡节点位置的左子树节点进行右旋完成平衡操作

结合情况三理解,直接看例子说明:
在这里插入图片描述

和情况三对称,不能难看,从先右旋再左旋变成了先左旋再右旋

最终该LR问题的代码如下:

// TODO 先左旋再右旋 导致失去平衡的节点在最小失衡树节点的左子树的右子树上
/**
        5                      5                      4
       / \                    / \                    / \
      2  6     对4节点左旋     4  6    对节点5右旋      2  5
     / \       -------->    /         -------->    / \  \
    1  4                   2                      1  3  6
      /                   / |
     3                   1  3

*/
public AVLNode<T> toLR(AVLNode<T> minImbalancedNode){
    // TODO 先左旋
    this.toRR(minImbalancedNode.left);
    // TODO 再右旋
    return this.toLL(minImbalancedNode.left);
}

完成对上面四种情况的学习后,下面正式开始来看看平衡二叉树的插入过程

2.4、平衡二叉树的插入过程

首先,在插入之前,该二叉树肯定是平衡的,这是前置条件也是必须条件。
然后,新的节点插入后,二叉树的平衡可能被打破了,如果打破了,就要从四种情况里面开始分析用何种情况来进行新平衡的维护。

来看一下大致的流程图:

在这里插入图片描述
那么来看看代码应该怎么写:

// TODO 查找某个指定值, 这个和二叉搜索树是一样的,就不做过多说明了
private boolean findNode(AVLNode<T> root,T value){
    // TODO 查找节点是否存在
    if(root == null){
        return false;
    }
    else if(root.value.compareTo(value) == 0){
        return true;
    }
    else if(root.value.compareTo(value) <= 0){
        return this.findNode(root.right,value);
    }
    return this.findNode(root.left,value);
}

public boolean find(T value){
    return this.findNode(root,value);
}
// TODO 辅助函数,计算以该节点为树的高度
public int getDepth(AVLNode<T> nowNode){
    if(nowNode == null){
        return 0;
    }
    return nowNode.height;
}

// TODO 二叉搜索树插入先
private AVLNode<T> binaryInsert(AVLNode<T> nowNode, T value){
    if(nowNode == null){
        nowNode = new AVLNode<>(value,0,null,null);
    }
    // 如果待插入的值小于当前节点的值,则遍历该节点的左子树
    else if(nowNode.value.compareTo(value) >= 0){
        nowNode.left = this.binaryInsert(nowNode.left,value);
        // TODO 完成插入后,如果跟节点的左右子树高度差大于等于2,则树不平衡
        if(Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right)) >= 2){
            // TODO 进一步判断使用LL还是LR
            // 如果需要插入的值小于当前节点左子树的值,则说明就是往该节点的左子树的左子树上插入
            // 说明这肯定是LL操作
            if(value.compareTo(nowNode.left.value) < 0){
                return this.toLL(nowNode);
            }
            // 反之就是LR操作
            return this.toLR(nowNode);
        }
    }
    // 如果待插入的值大于当前节点的值,则遍历该节点的右子树
    else if(nowNode.value.compareTo(value) < 0){
        nowNode.right = this.binaryInsert(nowNode.right,value);
        // TODO 完成插入后,如果跟节点的左右子树高度差大于等于2,则树不平衡
        if(Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right)) >= 2){
            // TODO 进一步判断使用RR还是RL
            // 如果需要插入的值大于当前节点右子树的值,则说明就是往该节点的右子树的右子树上插入
            // 说明这肯定是RR操作
            if(value.compareTo(nowNode.right.value) > 0){
                return this.toRR(nowNode);
            }
            // 反之就是RL操作
            return this.toRL(nowNode);
        }
    }
    // 计算以该节点为根的树的高度
    nowNode.height = Math.max(getDepth(nowNode.left),getDepth(nowNode.right)) + 1;
    return nowNode;
}

public AVLNode<T> insert(T value){
    // TODO 不允许插入重复值
    if(this.find(value)){
        nodeCount.incrementAndGet();
        return this.binaryInsert(root,value);
    }
    return null;
}
2.5、平衡二叉树的删除过程

我们知道,插入过程增加新节点会打破平衡,但是删除节点也会打破平衡,而且删除操作比插入操作要复杂一点。

我可以很负责任的说,平衡二叉树的删除也大致分为四种情况,分别如下:

2.5.1、第一种删除情况

如果待删除的节点没有左子树,也没有右子树,则删除后向上回溯找到最小不平衡树,对该树进行平衡维护即可
简单来说,就是删除叶子节点(不懂叶子节点百度吧,相信学到这里的同学都懂吧)

老道理,概念晦涩难懂,直接看图说话:
首先我们要了解一个道理,一棵二叉树平衡的条件是任意节点的左右子树高度差不超过1对吧,那么对于此种情况,那这个高度差肯定就有三种情况,
① 第一种:左右子树高度差为 -1,也就是右子树比左子树高1
② 第二种:左右子树高度差为0,也就是左子树高度等于右子树高度
③ 第三种:左右子树高度差为1,也就是左子树高度比右子树高1
按照上面三种情况,我们一一列举:

在这里插入图片描述

在这里插入图片描述
第三种情况就不做过多说明了,和第一种相反而已。

上面三种情况相信不难理解。其中第三种与第一种是对称操作,这和LL与RR操作对称是类似的。

2.5.2、第二种删除情况

如果待删除的节点有左子树,但没有右子树,则删除后向上回溯找到最小不平衡树,对该树进行平衡维护即可
简单来说,就是删除带有单亲的非叶子节点

那这个高度差肯定也还是有三种情况,
① 第一种:左右子树高度差为 -1,也就是右子树比左子树高1
② 第二种:左右子树高度差为0,也就是左子树高度等于右子树高度
③ 第三种:左右子树高度差为1,也就是左子树高度比右子树高1
按照上面三种情况,我们一一列举:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
可以看出,这三种情况都不需要进行平衡调整,删除后二叉树仍然是平衡的。

2.5.3、第三种删除情况

如果待删除的节点有右子树,但没有左子树,则删除后向上回溯找到最小不平衡树,对该树进行平衡维护即可
简单来说,就是删除带有单亲的非叶子节点

第三种情况与第二种情况是一样的,只不过是节点的位置不一样而已,与第二种情况对称,所以这里不做过多说明,参照第二种情况理解就可以。

2.5.4、第四种删除情况

如果待删除的节点既有右子树,也有左子树,则删除后向上回溯找到最小不平衡树,对该树进行平衡维护即可
简单来说,就是删除带有双亲的非叶子节点

第四种情况是删除里面的重点也是难点,而且有一些注意事项。
按照规律,我们继续分为三类:
① 第一种:左右子树高度差为 -1,也就是右子树比左子树高1
② 第二种:左右子树高度差为0,也就是左子树高度等于右子树高度
③ 第三种:左右子树高度差为1,也就是左子树高度比右子树高1
按照上面三种情况,我们一一列举:

在这里插入图片描述

可以看出,对于第一种情况,有两种维持平衡的方法,第一种维持的方法取:

取最小不平衡树根节点的左子树内最大节点替换原根节点,并删除其最大节点,此时树平衡了

第二种维持的方法是取:

取最小不平衡树根节点的右子树内最小节点替换原根节点,并删除其最小节点,此时树并没有平衡,还需要进行一次旋转才达到平衡

可以看出,无论是哪种方法,都可以巧妙地转换为删除叶子节点,这也就回到了情况一,至于选何种方法进行维持平衡,当然能不旋转就不旋转了,方法一最佳了

在这里插入图片描述
对于情况二而言,我们发现其也有两种调整平衡方式,第一种删除后仍然是平衡的,第二种还需要调整右子树达到平衡,而且第一种不再是删除叶子节点,而是删除单亲节点,但是单亲节点的删除也比较简单,直接把单亲的子节点替换原单亲节点,再删除其子节点就可以。

情况三我就不再做过多说明了,因为情况三属于情况一的对称操作,无非就是左右子树互换了而已,相信大家能够自己理解!!!,自学能力还是要有的。

我们最后来总结一下删除的规律吧;

① 对于删除双亲节点的操作而言,实际上调整的方式有两种

  • 将原不平衡节点替换为其左子树最大值节点,调整平衡
  • 将原不平衡节点替换为其右子树最小值节点,调整平衡

② 关于选用何种调整方式,理论上都可以,但是我们要为实际效率考虑,要是能调整后仍然是平衡的那岂不美哉,对吧,所以尽量选删除后不需要进行多余旋转调整的方式。
③ 那两种方式哪一种才不需要调整呢,我自己总结的规则如下:

  • 假设A是最小不平衡双亲节点,现在要对它执行删除操作,其左子树根节点为AL,右子树根节点是AR,我们只需要比较AL的右子树的高度H1与AR的左子树的高度H2即可
    如果 H1 > H2,则选择A的左子树的最大节点进行替换原不平衡节点
    如果 H1 < H2,则选择A的右子树的最小节点进行替换原不平衡节点
    如果 H1 = H2,则随便选择哪一种都可以

代码如下:

// TODO 获取某节点下的最小节点
public AVLNode<T> getMinChildNode(AVLNode<T> nowNode){
    if(nowNode == null) return null;
    else if(nowNode.left == null) return nowNode;
    else return getMinChildNode(nowNode.left);
}

// TODO 获取某节点下的最大节点
public AVLNode<T> getMaxChildNode(AVLNode<T> nowNode){
    if(nowNode == null) return null;
    else if(nowNode.right == null) return nowNode;
    else return getMinChildNode(nowNode.right);
}

private AVLNode<T> remove(AVLNode<T> nowNode, T value){
        if(nowNode == null){
            return null;
        }
        AVLNode<T> res = null;
        // TODO 待删除的节点在右子树
        if(nowNode.value.compareTo(value) < 0){
            nowNode.right = this.remove(nowNode.right,value);
            // TODO 如果删除之后平衡失调了
            if(Math.abs(nowNode.left.height - nowNode.right.height) >= 2){
                // TODO 由于删除的节点在右子树,取出该节点的左子树
                AVLNode<T> leftChild = nowNode.left;
                // TODO 如果leftChild的左子树节点高度高于其右子树高度
                if(getDepth(leftChild.left) > getDepth(leftChild.right)){
                    res = this.toRL(nowNode);
                }
                res = this.toLL(nowNode);
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }
        else if(nowNode.value.compareTo(value) > 0){
            nowNode.left = this.remove(nowNode.left,value);
            // TODO 删除之后平衡失调了
            if(Math.abs(nowNode.left.height - nowNode.right.height) >= 2){
                // TODO 由于删除的节点在左子树,取出该节点的右子树
                AVLNode<T>  leftChild = nowNode.left;
                // TODO 如果leftChild的左子树节点高度高于其右子树高度
                if(getDepth(leftChild.right) > getDepth(leftChild.right)){
                    res = this.toLR(nowNode);
                }
                res = this.toRR(nowNode);
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }else{
            // TODO 此时找到了待删除的节点,也就是nowNode
            // TODO 待删除节点的左右子树均不为空
            if(nowNode.left!= null && nowNode.right!=null){
                 /** TODO 如果左子树高度高于右子树
                 *   TODO ① 找到该节点左子树的最大值
                          ② 用该最大值替换掉待删除节点的位置
                  *       ③ 删除该左子树的最大值节点
                 */
                if(getDepth(nowNode.left) > getDepth(nowNode.right)){
                    AVLNode<T> leftMaxChild = getMaxChildNode(nowNode.left);
                    nowNode.value = leftMaxChild.value;
                    this.remove(nowNode,leftMaxChild.value);
                }else{
                    AVLNode<T> rightMinChild = getMinChildNode(nowNode.right);
                    nowNode.value = rightMinChild.value;
                    this.remove(nowNode,rightMinChild.value);
                }
            }else{
                res = nowNode.left == null?nowNode.right:nowNode.left;
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }

        return res;
    }


    // TODO 删除某个节点,要先判断该节点是否存在
    /**
     * TODO 删除分四种情况:
     *    ① 删除叶子节点,直接删除即可
     *    ② 删除非叶子节点,且该节点仅有左子树
     *    ③ 删除非叶子节点,且该节点仅有右子树
     *    ④ 删除叶子节点, 且该节点有左右子树
    */
    public AVLNode<T> remove(T value){
        if(this.find(value)){
            nodeCount.decrementAndGet();
            return this.remove(root,value);
        }
        return null;
    }

三、总结

平衡二叉树是为了应对二叉搜索树插入、查找、删除效率而生的,但是它本身也存在一些问题,那就是它的结构变化可能非常频繁,也就是插入与删除操作会非常频繁地跳转树的拓扑结构,所以后面又出现了红黑树,那这就是下节的知识了

如有问题,欢迎指出。

完整的代码:

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: ysj
 * @Description: 平衡二叉树
 *      性质:
 *          TODO ① ALV树是基二叉搜索树改进的
 *          TODO ② ALV树的左子树与右子树都是平衡二叉树
 *          TODO ③ ALV树的左右子树高度差的绝对值不能大于1
 * @Date Created in 2022-03-04-13:34
 */
@SuppressWarnings("all")
public final class MyAVLTree<T extends Comparable> implements Serializable {
    private AVLNode<T> root = null;
    private AtomicInteger nodeCount = new AtomicInteger(0);

    public MyAVLTree(){
        this(null);
    }

    public MyAVLTree(T rootValue){
        if(rootValue != null){
            root = new AVLNode<>(rootValue,0,null,null);
            nodeCount.incrementAndGet();
        }
    }

    // TODO 辅助函数,计算以该节点为树的高度
    public int getDepth(AVLNode<T> nowNode){
        if(nowNode == null){
            return 0;
        }
        return nowNode.height;
    }

    // TODO 二叉搜索树插入先
    private AVLNode<T> binaryInsert(AVLNode<T> nowNode, T value){
        if(nowNode == null){
            nowNode = new AVLNode<>(value,0,null,null);
        }
        else if(nowNode.value.compareTo(value) >= 0){
            nowNode.left = this.binaryInsert(nowNode.left,value);
            if(Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right)) >= 2){
                // TODO 进一步判断使用LL还是LR
                if(value.compareTo(nowNode.left.value) <= 0){
                    return this.toLL(nowNode);
                }
                return this.toLR(nowNode);
            }
        }
        else if(nowNode.value.compareTo(value) < 0){
            nowNode.right = this.binaryInsert(nowNode.right,value);
            if(Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right)) >= 2){
                // TODO 进一步判断使用RR还是RL
                if(value.compareTo(nowNode.right.value) > 0){
                    return this.toRR(nowNode);
                }
                return this.toRL(nowNode);
            }
        }
        nowNode.height = Math.max(getDepth(nowNode.left),getDepth(nowNode.right)) + 1;
        return nowNode;
    }

    // TODO 检查一棵二叉树是否平衡, nowNode是新插入的节点,或者是新删除的节点的父节点
    public boolean checkIsBalanced(){
        return this.checkIsBalanced(root);
    }

    // TODO 以nowNode为根节点的树是否是一个平衡二叉树
    public boolean checkIsBalanced(AVLNode<T> nowNode){
        if(nowNode == null){
            return true;
        }
        return checkIsBalanced(nowNode.left) && checkIsBalanced(nowNode.right) && (Math.abs(getDepth(nowNode.left) - getDepth(nowNode.right))<= 1);
    }

    public AVLNode<T> insert(T value){
        // TODO 不允许插入重复值
        if(this.find(value) == false){
            nodeCount.incrementAndGet();
            return this.binaryInsert(root,value);
        }
        return null;
    }

    // TODO 查找某个指定值, 这个和二叉搜索树是一样的,就不做过多说明了
    private boolean findNode(AVLNode<T> root,T value){
        // TODO 查找节点是否存在
        if(root == null){
            return false;
        }
        else if(root.value.compareTo(value) == 0){
            return true;
        }
        else if(root.value.compareTo(value) <= 0){
            return this.findNode(root.right,value);
        }
        return this.findNode(root.left,value);
    }

    public boolean find(T value){
        return this.findNode(root,value);
    }

    // TODO 获取某节点下的最小节点
    public AVLNode<T> getMinChildNode(AVLNode<T> nowNode){
        if(nowNode == null) return null;
        else if(nowNode.left == null) return nowNode;
        else return getMinChildNode(nowNode.left);
    }

    // TODO 获取某节点下的最大节点
    public AVLNode<T> getMaxChildNode(AVLNode<T> nowNode){
        if(nowNode == null) return null;
        else if(nowNode.right == null) return nowNode;
        else return getMinChildNode(nowNode.right);
    }

    private AVLNode<T> remove(AVLNode<T> nowNode, T value){
        if(nowNode == null){
            return null;
        }
        AVLNode<T> res = null;
        // TODO 待删除的节点在右子树
        if(nowNode.value.compareTo(value) < 0){
            nowNode.right = this.remove(nowNode.right,value);
            // TODO 如果删除之后平衡失调了
            if(Math.abs(nowNode.left.height - nowNode.right.height) >= 2){
                // TODO 由于删除的节点在右子树,取出该节点的左子树
                AVLNode<T> leftChild = nowNode.left;
                // TODO 如果leftChild的左子树节点高度高于其右子树高度
                if(getDepth(leftChild.left) > getDepth(leftChild.right)){
                    res = this.toRL(nowNode);
                }
                res = this.toLL(nowNode);
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }
        else if(nowNode.value.compareTo(value) > 0){
            nowNode.left = this.remove(nowNode.left,value);
            // TODO 删除之后平衡失调了
            if(Math.abs(nowNode.left.height - nowNode.right.height) >= 2){
                // TODO 由于删除的节点在左子树,取出该节点的右子树
                AVLNode<T>  leftChild = nowNode.left;
                // TODO 如果leftChild的左子树节点高度高于其右子树高度
                if(getDepth(leftChild.right) > getDepth(leftChild.right)){
                    res = this.toLR(nowNode);
                }
                res = this.toRR(nowNode);
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }else{
            // TODO 此时找到了待删除的节点,也就是nowNode
            // TODO 待删除节点的左右子树均不为空
            if(nowNode.left!= null && nowNode.right!=null){
                 /** TODO 如果左子树高度高于右子树
                 *   TODO ① 找到该节点左子树的最大值
                          ② 用该最大值替换掉待删除节点的位置
                  *       ③ 删除该左子树的最大值节点
                 */
                if(getDepth(nowNode.left) > getDepth(nowNode.right)){
                    AVLNode<T> leftMaxChild = getMaxChildNode(nowNode.left);
                    nowNode.value = leftMaxChild.value;
                    this.remove(nowNode,leftMaxChild.value);
                }else{
                    AVLNode<T> rightMinChild = getMinChildNode(nowNode.right);
                    nowNode.value = rightMinChild.value;
                    this.remove(nowNode,rightMinChild.value);
                }
            }else{
                res = nowNode.left == null?nowNode.right:nowNode.left;
                // TODO 重新调整树的高度
                res.height = Math.max(getDepth(res.left),getDepth(res.right)) + 1;
            }
        }

        return res;
    }


    // TODO 删除某个节点,要先判断该节点是否存在
    /**
     * TODO 删除分四种情况:
     *    ① 删除叶子节点,直接删除即可
     *    ② 删除非叶子节点,且该节点仅有左子树
     *    ③ 删除非叶子节点,且该节点仅有右子树
     *    ④ 删除叶子节点, 且该节点有左右子树
    */
    public AVLNode<T> remove(T value){
        if(this.find(value)){
            nodeCount.decrementAndGet();
            return this.remove(root,value);
        }
        return null;
    }

    // TODO 右旋一次, 返回该子树净调整后新的根节点
    // TODO 导致失去平衡的节点在最小失衡树节点的左子树的左子树上,就使用LL旋转
    /**
      *      6                       4
     *      / \                     / \
     *     4  10     右旋           3  6
     *    / \       ---->         /  / \
     *   3  5                    1  5  10
     *  /
     * 1
    */
    public AVLNode<T> toLL(AVLNode<T> minImbalancedNode){
        AVLNode im_left = minImbalancedNode.left;
        AVLNode pre_right = im_left.right;
        im_left.right = minImbalancedNode;
        minImbalancedNode.left = pre_right;
        return im_left;
    }

    // TODO 导致失去平衡的节点在最小失衡树节点的右子树的右子树上,左旋一次
    public AVLNode<T> toRR(AVLNode<T> minImbalancedNode){
        AVLNode im_right = minImbalancedNode.right;
        AVLNode pre_left = im_right.left;
        im_right.left = minImbalancedNode;
        minImbalancedNode.right = pre_left;
        return im_right;
    }

    // TODO 先左旋再右旋 导致失去平衡的节点在最小失衡树节点的左子树的右子树上,就使用LL旋转
    // TODO 左旋的是最小不平衡节点的左节点,然后继续右旋最开始不平衡的节点
    /**
            5                      5                      4
           / \                    / \                    / \
          2  6     对4节点左旋     4  6    对节点5右旋      2  5
         / \       -------->    /         -------->    / \  \
        1  4                   2                      1  3  6
          /                   / |
         3                   1  3

    */
    public AVLNode<T> toLR(AVLNode<T> minImbalancedNode){
        // TODO 先左旋
        this.toRR(minImbalancedNode.left);
        // TODO 再右旋
        return this.toLL(minImbalancedNode.left);
    }

    // TODO 失去平衡的节点在最小失衡树节点的右子树的左子树上
    public AVLNode<T> toRL(AVLNode<T> minImbalancedNode){
        // TODO 先右旋
        this.toLL(minImbalancedNode.right);
        // TODO 再左旋
        return this.toRR(minImbalancedNode.right);
    }
}

// TODO 平衡二叉树节点类
@SuppressWarnings("all")
class AVLNode<T extends Comparable> implements Serializable ,Comparable{
    T value;                 // TODO 该节点的值
    int height;              // TODO 以该节点为根的树的树高
    AVLNode<T> left;         // TODO 该节点的左子树
    AVLNode<T> right;        // TODO 该节点的右子树

    public AVLNode(T value, int height, AVLNode<T> left, AVLNode<T> right) {
        this.value = value;
        this.height = height;
        this.left = left;
        this.right = right;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public void setLeft(AVLNode<T> left) {
        this.left = left;
    }

    public void setRight(AVLNode<T> right) {
        this.right = right;
    }

    // TODO 重新compareTo是为了方便对节点内的值进行比较,因为节点是值是引用对象,而不是普通对象,不能直接比较
    @Override
    public int compareTo(Object o) {
        if(this.value != null && o!=null && o instanceof AVLNode){
            AVLNode<T> node = (AVLNode)o;
            if(node.value.compareTo(this.value) == 0) return 0;
            return node.value.compareTo(this.value)>0?1:-1;
        }
        throw new ClassCastException("比较对象的所属类不匹配!");
    }
}

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨小街

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值