AVL树(平衡二叉树)

概念:是一种二叉排序树,又称平衡二叉树,其中每一个节点的左子树和右子树的高度相差至多等于1

平衡因子:树上节点的左子树的深度减去右子树的深度的值

树上所有节点中,如果有一个节点的平衡因子的绝对值大于1 ,树就失去了平衡。

最小不平衡子树:距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树。

左转,右转:当树失去平衡后,我们可以通过节点的旋转来使其达到平衡。

 

例:有一棵树如图(1),当添加一个节点后( 如图(2) ),那么根节点的平衡因子为2,失去了平衡,此时就需要旋转。

旋转时依一个三角形为模型(即根节点,左子节点,右子节点组成的形状)如图(3),旋转后2变成了根节点,此时树再次恢复平衡。

不同的树恢复平衡时需要不同的操作,有如下几种情况:

左平衡

1,当新加入的节点在A的左孩子的左子树上,此时各个节点的平衡因子分别为:F=0  D=1 E=0 B=1 C=0 A=2,可知在A处打破了平衡状态,由于左子树层级过深引起所以要以A为根节点进行右旋转,旋转后如图2,此时变成了三叉树,违背了二叉排序树,所以E要放到别的地方,由于是排序树,所以E 大于 B 并且 E小于A,所以E应该放到A的左孩子,最后如图3,此时各个结点的平衡因子分别为:F=0  D=1 E=0 B=0 C=0 A=0,再次恢复平衡。

2.当新加入的节点在A的左孩子的右子树上,此时又分三种情况分别是A的左孩子的右子树的根节点的平衡因子为1,A的左孩子的右子树的根节点的平衡因子为-1,A的左孩子的右子树的根节点的平衡因子为0。

(1),加入结点后A的左孩子的右子树的根节点的平衡因子为1,如图1,此时各个结点的平衡因子分别为:F=0  D=0 E=1 B=-1 C=0 A=2,这种情况需要先以B为根节点进行左旋转,旋转后如图2,由于F小于E,F大于B, 所以E要放到B的右子树。

然后再以A为根节点进行右旋转,此时的状态等同于第一种新加入的节点在A的左孩子的左子树上的情况,所以直接进行右旋转即可,旋转后如图3,此时各个结点的平衡因子分别为:F=0  D=0 E=0 B=0 C=0 A=-1,再次恢复平衡。

(2),加入结点后A的左孩子的右子树的根节点的平衡因子为 -1,如图1,此时各个结点的平衡因子分别为:F=0  D=0 E=-1 B=-1 C=0 A=2,这种情况同上面(1)一样也需要先进行左旋转再进行右旋转,只不过这种情况下F大于E,不需要进行结点移动,旋转后如图2,现在这种状态也和第一种情况一样,直接进行右旋转,如图3,此时各个结点的平衡因子分别为:F=0  D=0 E=0 B=1 C=0 A=0,再次恢复平衡。

(3),加入结点后A的左孩子的右子树的根节点的平衡因子为 0,如图1,和上面套路一样,先进行左旋转再进行右旋转,此时各个结点的平衡因子为:G=0 F=0  D=0 E=0 B=-1 C=0 A=2,左旋转后如图2,然后进行右旋转,如图3,此时各个结点的平衡因子为:G=0 F=0  D=0 E=0 B=0 C=0 A=0,恢复平衡

这里发现一个问题:虽然分了三种情况,但是每种情况都是先左转再右转,分类又有什么用呢?

仔细看一下每种情况旋转后各个结点最终的平衡因子就会发现分类的作用,其实就是用来修改平衡因子。

右平衡

 和左平衡一样,也分同样的几种情况,其实把做平衡的逻辑对称过来就是右平衡。

1,当新加入的节点在A的右孩子的右子树上,那么直接进行左旋转,和方面套路一样,直接看图,加入F后打破平衡,此时各个结点的平衡因子为:A=-2 B=0 C=-1 D=0 E=-1 F=0,进行左旋转后如图2,此时各个结点的平衡因子为:A=0 B=0 C=0 D=0 E=-1 F=0,恢复平衡。

2,当新加入的节点在A的右孩子的左子树上,这里也分三种情况:A的右孩子的左子树的根节点的平衡因子为1,A的右孩子的左子树的根节点的平衡因子为-1,A的右孩子的左子树的根节点的平衡因子为0。

(1)A的右孩子的左子树的根节点的平衡因子为1,如图1,此时各个结点的平衡因子为:A=-2 B=0 C=1 D=1 E=0 F=0,右旋转后如图2,最后进行左旋转如图3,此时各个结点的平衡因子为:A=0 B=0 C=-1 D=0 E=0 F=0。

 

(2)A的右孩子的左子树的根节点的平衡因子为-1,如图1,此时各个结点的平衡因子为:A=-2 B=0 C=1 D=-1 E=0 F=0,右旋转后如图2,最后进行左旋转完成平衡,此时各个结点的平衡因子为:A=1 B=0 C=0 D=0 E=0 F=0。

(2)A的右孩子的左子树的根节点的平衡因子为 0,如图1,此时各个结点的平衡因子为:A=-2 B=0 C=1 D=0 E=0 F=0 G=0,,右旋转后如图2,最后进行左旋转完成平衡,此时各个结点的平衡因子为:A=0 B=0 C=0 D=0 E=0 F=0 G=0,。

按照上面的逻辑编写如下代码:

/**
 * 左旋转
 */
public void leftRotate(AVLBNode x) {
    if (x == null) return;
    AVLBNode y = x.rightNode;
    if (x.parent == null) {
        rootNode = y;
    } else {
        if (x.parent.leftNode == x) { //为父节点的左孩子
            //把父节点的左孩子指向自己的右结点
            x.parent.leftNode = y;
        }
        if (x.parent.rightNode == x) {//为父节点的右孩子
            //把父节点的右孩子指向自己的右结点
            x.parent.rightNode = y;
        }
    }
    //把x的父节点指向x的右孩子
    x.parent = y;
    x.rightNode = y.leftNode;
    //把x的右结点的左结点的父节点指向x。
    if (y.leftNode != null) {
        y.leftNode.parent = x;
    }
    //把x的右结点的左结点指向x
    y.leftNode = x;

}

 

/**
 * 右旋转操作
 *
 * @param y
 */
public void rightRotate(AVLBNode<E> y) {

    if (y != null) {
        //获取到x的左子节点
        AVLBNode<E> y1 = y.leftNode;
        //1
        //把x的左结点指向y的右结点
        y.leftNode = y1.rightNode;
        if (y1.rightNode != null) {
            y1.rightNode.parent = y;
        }
        //2
        //把y的父节点指向x的父节点
        y1.parent = y.parent;
        if (y.parent == null) {
            rootNode = y1;
        } else {
            if (y.parent.leftNode != null) {
                if (y.parent.leftNode == y) { //x为父节点的左子节点
                    //把父节点的左孩子指向自己的左结点
                    y.parent.leftNode = y1;
                }
            }

            if (y.parent.rightNode != null) {
                if (y.parent.rightNode == y) {//x为父节点的右子节点
                    //把父节点的右孩子指向自己的左结点
                    y.parent.rightNode = y1;
                }
            }
        }

        //3
        //把y的右孩子指向x
        y1.rightNode = y;
        y.parent = y1;

    }
}

 

/**
 * 左平衡操作
 *
 * @param t
 */
private void leftBalance(AVLBNode<E> t) {
    AVLBNode<E> t1 = t.leftNode;
    switch (t1.balance) {
        case RH:
            //被插入的结点在t的右子树的左孩子
            AVLBNode<E> trl = t1.rightNode;
            if (trl==null) return;
            switch (trl.balance) {
                case RH:
                    trl.balance = EH;
                    t1.balance = LH;
                    t.balance = EH;
                    break;
                case LH:
                    trl.balance = EH;
                    t1.balance = EH;
                    t.balance = RH;
                    break;
                case EH:
                    trl.balance = EH;
                    t1.balance = EH;
                    t.balance = EH;
                    break;
            }
            //左转
            leftRotate(t.leftNode);
            //右转
            rightRotate(t);
            break;
        case LH:
            //直接右转
            rightRotate(t);
            t1.balance = EH;
            t.balance = EH;
            break;
    }
}
/**
 * 右平衡操作
 *
 * @param t
 */
private void rightBalance(AVLBNode<E> t) {
    AVLBNode<E> tr = t.rightNode;
    switch (tr.balance) {
        case RH:
            //被插入的结点在t的右子树的右孩子
            leftRotate(t);
            t.balance = EH;
            tr.balance = EH;
            break;
        case LH:
            //被插入的结点在t的右子树的左孩子
            AVLBNode<E> trl = tr.leftNode;
            //改变平衡因子
            switch (trl.balance) {
                case RH:
                    trl.balance = EH;
                    tr.balance = RH;
                    t.balance = LH;
                    break;
                case LH:
                    trl.balance = EH;
                    tr.balance = EH;
                    t.balance = RH;
                    break;
                case EH:
                    trl.balance = EH;
                    tr.balance = EH;
                    t.balance = EH;
                    break;
            }
            rightRotate(t.rightNode);
            leftRotate(t);
            break;

    }
}
/**
 * 插入结点
 * @param newData
 * @return
 */
public boolean insertNode(E newData) {
    AVLBNode<E> t = rootNode;
    if (t == null) {
        rootNode = new AVLBNode((Comparable) newData, 0, null, null, null);
        size = 1;
        return true;
    } else {
        //查找插入的位置
        int cmp = 0;
        AVLBNode<E> parent;
        Comparable<? super E> e = newData;
        int num = 0;
        do {
            parent = t;
            cmp = e.compareTo(t.elements);
            if (cmp < 0) {
                t = t.leftNode;
            } else if (cmp > 0) {
                t = t.rightNode;
            } else {
                return false;
            }
            if (num > 100) {
                t = null;
            } else {
                num++;
            }

        } while (t != null);
        //t为需要插入的位置,parent为它的父节点
        //找到插入的位置t
        //开始插入数据
        AVLBNode<E> node = new AVLBNode<>(newData, 0, null, null, parent);
        if (cmp < 0) {
            //如果插入的值小于当前父节点
            parent.leftNode = node;
        } else {
            //如果插入的值大于当前父节点
            parent.rightNode = node;
        }
        //修改平衡因子
        while (parent != null) {
            cmp = e.compareTo(parent.elements);
            if (cmp < 0) {
                parent.balance++;
            } else {
                parent.balance--;
            }
            if (parent.balance == 0) {
                break;
            }
            if (Math.abs(parent.balance) == 2) {
                //出现了平衡问题
                fixAfterInsertion(parent);
                break;
            } else {
                parent = parent.parent;
            }

        }

    }


    return false;
}

/**
 * 修正平衡树
 *
 * @param node
 */
public void fixAfterInsertion(AVLBNode<E> node) {
    if (node.balance == 2) {
        //左边层级过深进行左修正
        leftBalance(node);
    }
    if (node.balance == -2) {
        //右边层级过深进行右修正
        rightBalance(node);
    }

}

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值