AVL树

本文深入探讨了二叉搜索树在添加元素后的复杂度分析,特别关注最坏情况下的性能差距,并介绍了AVL树的平衡策略。通过LR/RL双旋操作,展示了如何通过旋转恢复平衡,包括左旋、右旋及双旋的详细步骤和代码实现。
摘要由CSDN通过智能技术生成

先看一下二叉搜索树的复杂度分分析

 当我们添加元素到二叉搜索树中时:

寻找的次数只与树的高度有关 与树的节点数并无大的关系

复杂度:O(logn)==O(h)

有点像二分  一半一半的排除查找   因此为O(logn)

但是也存在最坏的情况:

 

 当n非常大的时候

我们可以看出两种情况下搜索的性能相差甚远

解决办法: 

 平衡:

所谓更平衡就是:左右子树的高度差越来越小

 如何改进平衡二叉树?

改进的规则:

 

AVL树:

 记住顺序:平衡因子是左子树的高度-右子树的高度

举个例子:7的平衡因子==-2

7的左子树高度为3  右子树高度为 5

3-5==-2

AVL树的特点:

 AVL树的平衡因子的绝对值一旦超过1

我们称之为失衡

 当我们添加节点导致的失衡

我们添加的时候是在下面角落处进行添加的

肯定不可能在上面度为2的地方进行添加

因为二叉树是最大度为2的树

当我们由于添加节点元素导致失衡时

如何进行弥补改正呢? 

首先给出一组

这一组只是一整棵树的下面一小部分

还是那句话我们添加就是针对下面的一小块区域进行的操作

 注意水平线:T0  T1    T2   T3的树节点的高度是不同的

那么我们现在可知:平衡因子是左高减右高

n的平衡因子是0

p的平衡因子是0

g的平衡因子是1

LL单旋:进行右旋转  在左边的左边添加节点

为什么进行右旋转呢?

因为添加的节点是在g的左子树上

当我们添加红色节点段时:g失去平衡

此时让g失去平衡的是它左边的左边的T0处的添加

因此我们进行右旋转

如图:

 一开始假设这是一颗平衡的二叉搜索树

并且所有的节点都是位于红色线的上面

当我们添加元素之后肯定会打破这个平衡

当我们添加了一个节点之后  发现一个节点超过了红线

由于这个是在左边的左边

即是T0或T1处加上的一个节点

那么要进行右旋转

g.left=p.right

p.right=g

这时候你会发现所有的节点都到红线的上方了

那么此时又是一颗平衡二叉搜素树了  (根节点p上面的部分依旧是平衡的)

 

除此之外我们还要把节点之间的属性改一下 

代码如下:

  //包装右旋转的方法
    private void rotateRight(Node<E> grand){
Node<E> parent=grand.left;
Node<E> child=parent.left;
grand.left=child;
parent.right=grand;
//让parent代替相应的父节点 更新parent的parent
        parent.parent=grand.parent;
        if(grand.isLeftChild()){
            grand.parent.left=parent;
        } else if(grand.isRightChild()){
          grand.parent.right=parent;
        } else {
            root=parent;
        }
        //更新child的parent
        if(child!=null){
            child.parent=grand;
        }
        //更新grand的parent
        grand.parent=parent;
        //切记更改各个parent的顺序  先改parent  再child  最后grand

        //更新高度
        updateHeight(grand);
        updateHeight(parent);
    }

 RR单旋进行左旋转  在右边的右边添加节点

为什么进行左旋转呢?

因为添加的节点是在g的右子树上

代码如下:

  //包装左旋转的方法
    private void rotateLeft(Node<E> grand){
        //把grand指向的parent拿出来
        Node<E> parent=grand.right;
      Node<E> child=parent.left;
        grand.right=child;
         parent.left=grand;
         //让parent代替grand成为相对的父节点  更新parent的parent
        parent.parent=grand.parent;
        if(grand.isLeftChild()){//grand是左子树时
            grand.parent.left=parent;
        } else if(grand.isRightChild()){
            grand.parent.right=parent;
        } else {//说明grand上面没有节点了 grand就是根节点
            root=parent;
        }
        //更新child的parent
        if(child!=null){
            child.parent=grand;
        }
        //更新grand的parent
        grand.parent=parent;
        //切记更改各个parent的顺序  先改parent  再child  最后grand

        //更新高度
        updateHeight(grand);
        updateHeight(parent);
    }

 LR双旋:先进行左旋转再进行右旋转

这种情况下:在T2下面加上一个节点

如何分辨是什么双旋?

从g进来先往左再往右  left right

因此为LR双旋  前面我们有了左旋右旋基础

因此LR双旋 先进行左旋转再进行右旋转

1.先对p进行左旋转

为什么进行左旋转呢?

因为添加的节点是在p的右子树上

是指向右的

 2.对g进行右旋转

因为添加的节点是在g的左子树上

是指向左的

 RL双旋:

 从根节点开始先右再左

因此为RL双旋 

会在T1和T2进行添加节点元素

 1.对p先进行右旋转

为什么呢?

因为添加的节点是在p的左子树上

2.对g进行左旋转

因为添加的节点元素是在g的右子树上

因此指向右的

因此进行左旋转

 总结:

1.对二叉树进行双旋时 

先对相对下面的元素进行旋转再进行相对上面的节点进行旋转

如先对p进行旋转再对g进行旋转

2.还有一点就是对旋转方向的总结

我们旋转肯定是对一个节点进行旋转对吧?

那么举个例子来看:

进行双旋时遵守规则:

先旋相对下面的 再旋相对上面的

第一步旋p的时候  发现添加的节点是相对p这一个节点是在左子树的

因此进行右旋转操作

第二步旋g的时候

只看g这个单个节点可知添加的节点是在g的右子树上

因此我们对g进行左旋转

对于双旋的代码如下:

 //恢复平衡的方法
    //重点:
    在图中p对应parent g对应grand   n对应node
    //grand传进来的就是高度最低的不平衡节点
    private void rebalance(Node<E> grand){
        //这里传给grand的是树节点的node
        //然而这里的tallerChild是属于AVLNode<E>的方法
        //因此我们得强转之后才能调用

        //parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
        //上树?
        if(parent.isLeftChild()){//parent是grand左子树那么一开始是L
            if(node.isLeftChild()){//成立 那么node是左  L
                //那么就是LL
//那么就只是进行右旋转
                rotateRight(grand);//在图中p对应parent g对应grand   n对应node
            } else {//R
                //LR
//先对下面的不平衡节点进行右旋转
                rotateRight(parent);
                //再对较上面的不平衡节点进行左旋
                rotateLeft(grand);
            }
        } else {//一开始parent是grand的右子树 R
            if(node.isRightChild()){//R
                //那么就是RR
rotateLeft(grand);
            } else {//L
                //那么即是RL
                rotateLeft(parent);
                rotateRight(grand);
            }
        }
    }

在我们修复平衡二叉树时

从下往上找失衡的节点

找到n和p :

 private void rebalance(Node<E> grand){
        //这里传给grand的是树节点的node
        //然而这里的tallerChild是属于AVLNode<E>的方法
        //因此我们得强转之后才能调用

        //parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
    }

在图中p对应parent g对应grand n对应node

代码中对应标识:

总代码如下:

AVL树:

增加节点之后进行判断它是否仍是平衡二叉树

若是那么给它恢复平衡

afterAdd:

package BinaryTree;

import java.util.Comparator;

public class AVLTree<E> extends BST<E>{
    public AVLTree() {
        this(null);
    }
    public AVLTree(Comparator<E> comparator) {
        super(comparator);
    }
   @Override
    protected void addafter(Node<E> node) {
              while ((node=node.parent)!=null){
                  //如果平衡 更新高度
                  if(((AVLNode<E>)node).isbalance(node)){
                      ((AVLNode<E>)node).Updataheight();
                  } else {//不平衡 那么恢复平衡
                       rebalance(node);
                       break;//只要恢复了平衡直接结束afteradd
                  }
              }
    }

    /**
     *恢复平衡
     * 第一次传进来的node就是高度最低的不平衡的父节点
     */
    private void rebalance(Node<E> grand){
//因为添加节点之后 肯定高度增加了 因此我们要找左右子树较高的那一个
        Node<E> parent= ((AVLNode<E>)grand).tallerChild();
        Node<E> node= ((AVLNode<E>)parent).tallerChild();
        if(parent.isLeftChild()){//parent是grand的左子树
            if(node.isLeftChild()){//LL
                rotateright(grand);
            } else {//LR
                rotateleft(parent);
                rotateright(grand);
            }
        } else {//parent是grand的右子树
            if(node.isRightChild()){//RR
                rotateleft(grand);
            } else {//RL
                rotateright(parent);
                rotateleft(grand);
            }
        }
    }

    /**
     *封装左和右旋转的操作
     */
    //进行左旋转 默认grand.right=parent  parent.right=node
    private void rotateleft(Node<E> grand){
Node<E> parent=grand.left;
//1.先更新树的节点指向
        grand.right=parent.left;
        parent.left=grand;
        //2.更新父属性
        //虽然我们已经更新了链接方式 但是节点之间的父子关系还没有进行更新还是有线连着的
        //首先更新parent的父属性
        if(grand.isLeftChild()){
            grand.parent.left=parent;
        } else if(grand.isRightChild()) {
            grand.parent.right=parent;
        } else {//当原来没旋转之前 grand是整个AVL树的根节点
            root=parent;
        }
        //其次更新parent下面一个子树的父属性
        parent.left.parent=grand;
        //最后更新grand的父属性
        grand.parent=parent;
        //更新高度
        ((AVLNode<E>)grand).Updataheight(grand);
        ((AVLNode<E>)parent).Updataheight(parent);
    }
    private void rotateright(Node<E> grand){
            Node<E> parent=grand.left;
            //1.更新链接关系
        grand.left=parent.right;
        parent.right=grand;
        //2.更新父属性
        //先更新parent的父属性
        if(grand.isRightChild()){
            grand.parent.right=parent;
        } else {
            grand.parent.left=parent;
        }
        //再更新parent的一个子树的父属性
        parent.right.parent=grand;
        //最后更新grand的父属性
        //如果先更新grand的父属性
        // 前面的parent在更新父属性时grand就变化了 不可行
        grand.parent=parent;
        //更新高度
        ((AVLNode<E>)grand).Updataheight(grand);
        ((AVLNode<E>)parent).Updataheight(parent);
    }
    @Override
    protected Node<E> creatNode(E elements, Node<E> parent) {
        return new Node<E>(elements,parent);
    }
    /**
     *因为我们AVL相比BST多了许多
     * 因此我们创建一个内部类
     * 并且继承BST中创建出的Node<E>的全部属性
     */
   private static class  AVLNode<E> extends Node<E>{
        int height=1;//新加的节点一定是叶子节点
        public AVLNode(E elements, Node<E> parent) {
            super(elements, parent);
        }
        /**
         *封装一下 更新高度
         */
        private void Updataheight(Node<E> node){
            ((AVLNode<E>)node).Updataheight();
        }
        /**
         * 先每一个AVL节点都创建一个更新高度的方法
         */
        public int Updataheight(){
            int heightleft=left==null?0:((AVLNode<E>)left).height;
            int heightright=right==null?0:((AVLNode<E>)right).height;
            return 1+Math.max(heightleft,heightright);
        }
        /**
         * 判断是否平衡
         */
        private boolean isbalance(Node<E> node){
            return Math.abs(((AVLNode<E>)node).balanceFactor())<=1;
        }
        /**
         *计算AVL树的平衡因子
         */
        private int balanceFactor(){
            //因为这里我们AVL是继承的BST BST继承的BinaryTree
            //BinaryTree中是不具有height这个属性的 只有AVL树中才有
            //因此我们要把这个left和right属性强制转化为AVL节点类型
            int heightleft=left==null?0:((AVLNode<E>)left).height;
            int heightright=right==null?0:((AVLNode<E>)right).height;
            return heightleft-heightright;
        }
        public Node<E> tallerChild(){
            int heightleft=left==null?0:((AVLNode<E>)left).height;
            int heightright=right==null?0:((AVLNode<E>)right).height;
            if(heightleft<heightright){
                return right;
            }
            if (heightleft>heightright){
                return left;
            }
            //如果高度相等 返回相同方向的
            return isLeftChild()?left:right;
        }
    }
}

发现规律:

统一旋转代码:

 这四种情况最后的结果是一样的

我们只要搞清楚最后的abcdefg的情况是什么样的就行

创建一个方法时期适用于所有旋转

由完全二叉树的性质可设:

节点大小从小到大:abcdefg

我们实现这个朴实的方法:

 //之前未改变时的根节点
    private void rotate(Node<E> r,Node<E> a,Node<E> b,Node<E> c,Node<E> d,Node<E> e,Node<E> f,Node<E> g){
        //但是无论怎么做  d最后都是根节点

        //让d成为根节点
        d.parent= r.parent;
        if(r.isLeftChild()){
            r.parent.left=d;
        } else if(r.isRightChild()){
            r.parent.right=d;
        } else {
            root=d;
        }
        //在更新a b c的时候不仅要表面b的左子树是a  还要表明a的父节点是b
        //双双  表明才是算无遗策
        b.left=a;
        if(a!=null) {
            a.parent = b;
        }
        b.right=c;
        if(c!=null) {
            c.parent = b;
        }
        //更新一下b的高度
        updateHeight(b);
        //更新f e g
        f.left=e;
        if(e!=null) {
            e.parent = f;
        }
        f.right=g;
        if(g!=null){
            g.parent=f;
        }
        updateHeight(f);
    }

那么问题来了:

如何传参呢?

前面我们知道 

首先:abcdefg是从小到大依次排序起来的

然后 我们应该对应着各个旋转的图片进行把参数书写完毕

 RR单旋:

LR双旋:

RL双旋:

那么进行套就可以得出:

 private void rebalancetwo(Node<E> grand){
Node<E> parent=((AVLNode<E>)grand).tallerChild();
        Node<E> node=((AVLNode<E>)parent).tallerChild();
        if(parent.isLeftChild()){
            if(node.isLeftChild()){//LL
rotate(grand,node.left,node,node.right,parent,parent.right,grand,grand.right);
            } else {//LR
rotate(grand,parent.left,parent,node.left,node,node.right,grand,grand.right);
            }
        } else {
            if(node.isLeftChild()){//RL
rotate(grand,grand.left,grand,node.left,node,node.right,parent,parent.right);
            } else {//RR
rotate(grand,grand.left,grand,parent.left,parent,node.left,node,node.right);
            }
        }
    }             

那么我们这种方法的总代码:

//恢复平衡的方法
    //重点:
    在图中p对应parent g对应grand   n对应node
    //grand传进来的就是高度最低的不平衡节点
    private void rebalance(Node<E> grand){
        //这里传给grand的是树节点的node
        //然而这里的tallerChild是属于AVLNode<E>的方法
        //因此我们得强转之后才能调用

        //parent是grand左右子树最高的节点 grand传进来的就是高度最低的不平衡节点
Node<E> parent=((AVLNode<E>)grand).tallerChild();
//node是parent左右子树最高的节点
Node<E> node=((AVLNode<E>)parent).tallerChild();
//找到这两个节点之后
        //上树?
        if(parent.isLeftChild()){//parent是grand左子树那么一开始是L
            if(node.isLeftChild()){//成立 那么node是左  L
                //那么就是LL
//那么就只是进行右旋转
                rotateRight(grand);//在图中p对应parent g对应grand   n对应node
            } else {//R
                //LR
//先对下面的不平衡节点进行右旋转
                rotateRight(parent);
                //再对较上面的不平衡节点进行左旋
                rotateLeft(grand);
            }
        } else {//一开始parent是grand的右子树 R
            if(node.isRightChild()){//R
                //那么就是RR
rotateLeft(grand);
            } else {//L
                //那么即是RL
                rotateLeft(parent);
                rotateRight(grand);
            }
        }
    }
    private void rebalancetwo(Node<E> grand){
Node<E> parent=((AVLNode<E>)grand).tallerChild();
        Node<E> node=((AVLNode<E>)parent).tallerChild();
        if(parent.isLeftChild()){
            if(node.isLeftChild()){//LL
rotate(grand,node.left,node,node.right,parent,parent.right,grand,grand.right);
            } else {//LR
rotate(grand,parent.left,parent,node.left,node,node.right,grand,grand.right);
            }
        } else {
            if(node.isLeftChild()){//RL
rotate(grand,grand.left,grand,node.left,node,node.right,parent,parent.right);
            } else {//RR
rotate(grand,grand.left,grand,parent.left,parent,node.left,node,node.right);
            }
        }
    }                     //之前未改变时的根节点
    private void rotate(Node<E> r, Node<E> a, Node<E> b, Node<E> c, Node<E> d, Node<E> e, Node<E> f,Node<E> g){
        //但是无论怎么做  d最后都是根节点

        //让d成为根节点
        d.parent= r.parent;
        if(r.isLeftChild()){
            r.parent.left=d;
        } else if(r.isRightChild()){
            r.parent.right=d;
        } else {
            root=d;
        }
        //在更新a b c的时候不仅要表面b的左子树是a  还要表明a的父节点是b
        //双双  表明才是算无遗策
        b.left=a;
        if(a!=null) {
            a.parent = b;
        }
        b.right=c;
        if(c!=null) {
            c.parent = b;
        }
        //更新一下b的高度
        updateHeight(b);
        //更新f e g
        f.left=e;
        if(e!=null) {
            e.parent = f;
        }
        f.right=g;
        if(g!=null){
            g.parent=f;
        }
        updateHeight(f);
    }

删除:

在计算平衡因子的时候

我们删除16这个节点之后只会使它的父节点失衡

除此之外其他的都不会失衡

因为平衡因子的计算都是根据左右的高度差进行计算的

当父节点只有一个子节点的时候

我们删除这个子节点确实影响了父节点的高度

但是父节点的依然平衡

删除之后的操作:

AVLTree中:

 /**
     *重写removeafter
     */
    @Override
    protected void removeafter(Node<E> node) {
        while((node=node.parent)!=null){
            //如果平衡 更新高度
            if(((AVLNode<E>)node).isbalance(node)){
                ((AVLNode<E>)node).Updataheight();
            } else {//不平衡 那么恢复平衡
                rebalance(node);
            }
        }
    }

BST中:

/**
 * 通过下面的方法找到对应的节点之后
 * 我们进行删除操作
 * 设计模式:
 * 我们删除的时候 总共要删除三类节点:
 * 度为0 1 2的节点
 * 我们在删除度为2的节点时  不可以直接删除而是先拿前驱或后继节点的元素值去覆盖它
 * 然后在把用来的前驱或后继节点给删除
 * 这被删除的节点只可能是度为0或1的节点 这与前两类的删除模式相重复
 * 因此:
 * 我们先删除度为2的节点
 * 之后再处理后继或前驱节点时 也相当于处理度为1或0的节点了
 */
private void remove(Node<E> node) {
    if (node == null) {
        return;
    }
    //先删除度为2的节点
    if (node.left!=null&&node.right!=null) {
        Node<E> f = predecessor(node);//找到前驱节点
        node.elements = f.elements;//值覆盖
        node = f;
        //由于我们后面不只是为了度为2这一种情况而准备的
        //有可能传进来的node直接是度为1或0 那么我们不可以直接用f去处理
        //因此我们用node指向f所指向的节点
    }
    Node<E> replacement = node.left != null ? node.left : node.right;
    if (replacement != null) {  //度为1

        replacement.parent = node.parent;
        if (node.parent == null) {//度为1且为根节点
            root = replacement;
        } else if (node == node.parent.left) {//一定要用node这条线的判断指向 自己想想
            //replacement.parent = node.parent;
            node.parent.left = replacement;
        } else if (node == node.parent.right) {
            //replacement.parent = node.parent;
            node.parent.right = replacement;
            removeafter(node);
        }

        //到这已经代表replacement==null也就是说那个为了删除度为2节点去覆盖的是叶子节点
    } else if (node.parent == null) {
        root = null;//度为0 且为根节点
           removeafter(node);
        } else {//度为0 不是根节点
            if (node.parent.left == node) {
                node.parent.left = null;
            } else {//node.parent.right==node
                node.parent.right =null;
            }
            removeafter(node);
        }
    }

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值