树03_AVL树

概念

苏联科学家发明的
节点的平衡因子
节点左右子树的高度差

AVL树特点
每个节点的平衡因子<1
增删改查的时间复杂度都是O(logN)
在这里插入图片描述
二叉树\二叉搜索树\AVL树继承结构
在这里插入图片描述
AVL树和红黑树都是二叉搜索树

AVL树添加操作

由于节点的添加是随机的,没法干预,只能再节点添加或者删除操作做完之后,对此节点进行调整,从而维持树的平衡.
(1)在添加节点之后增加了 afterAdd() 用于调整平衡;
(2)在删除节点之后增加了 afterRemove() 用于调整平衡;

1.添加导致失衡分析

在这里插入图片描述
(1)添加节点,不会导致直接父节点失衡

父节点有两种情况:叶子结点或者度为1,二者添加子节点都不会失衡。

(2)祖先节点可能会失衡

1.当祖先节点的左右子树的高度差为0,添加不会导致失衡。
2.当祖先节点的左右子树高度差为1,添加可能会导致失衡,并且会造成连锁反应。

(3)非祖先节点都不会失衡

2.修复平衡的操作

LL – 右旋转(单旋)
RR – 左旋转(单旋)
LR – 先左旋,再右旋(双旋)
RL – 先右旋,再左旋(双旋)

经过旋转后仍然是二叉平衡树

有些教程里面:
• 把右旋转叫做 zig,旋转之后的状态叫做 zigged
• 把左旋转叫做 zag,旋转之后的状态叫做 zagged

(1) LL- 右旋转

前提:

添加节点的父节点:n
n的父节点p
p的父节点g

在这里插入图片描述
右旋转的情况:

n = p.left
p = g.left
n节点下添加了新的节点,导致g节点失衡

处理方法:右旋转

	让p的右子树变为g的左子树
	g变为p的右子节点

右旋:变成当前节点左子节点的右子节点
右旋的特点:对g进行右旋转,我们假设g的父节点为G,那么旋转后会发现G的子节点为p,并且G子树由添加后失衡变为回复平衡;回复平衡后的G子树的高度和添加节点前的高度是一样的,因此整个二叉树恢复了平衡。
在这里插入图片描述

(2) RR – 左旋转(单旋)

在这里插入图片描述
RR情况:

	n = p.right
	p = g.right

失衡节点:g
处理方式:g节点左旋
左旋特点:g变成右子节点的左子节点

(3)LR

在这里插入图片描述

LR处理方式: 
		先对p进行左旋 ,变成LL
		在对g进行右旋

LR特点:n变为子节点

(4)LR

在这里插入图片描述

添加后修复总结

  • 修复操作核心:不改变BST性质
  • 修复操作特点:对于添加新节点后的失衡节点g来说,修复平衡后g的高度是不变的!
	由于修复后,g高度不变,因此g的祖先节点在g恢复平衡之后就恢复平衡了
	那么我们只需要修复g点即可!

3.afterAdd

修复平衡的操作是在一个新的节点添加进去完成后,开始进行修复;
因此需要在add()方法中 元素成功添加进去之后添加一个afterAdd()进行修复

1.二叉搜索树代码调整:

1.在二叉搜索树中添加一个afterAdd()方法,但是自身不实现这个方法
2.继承的AVL树中实现afterAdd()方法即可
如此一来,AVL树能实现平衡而不影响二叉搜索树

(1)添加方法:

  //todo 提供一个add后的操作,普通BST中不做任何实现
    protected void afterAdd(Node<E> node){}

(2)修改add()方法: 尾部插入afterAdd()

   public void add(E element){
        elementNotNullCheck(element);
        if(root == null){
            //root = new Node(element,null);
            root = createNode(element,null);
            size++;
            afterAdd(root);
            return;
        }
        //1.新节点插到哪个节点下面?
        //2.新节点插到该节点的左边还是右边?
        Node<E> node = root;
        Node<E> parent = null;
        int cmp = 0;
        while(node!=null){
            parent = node;
            cmp = Compare(node.element,element);
            if(cmp > 0){
                node = node.left;
            }else if(cmp < 0){
                node = node.right;
            }else{
                return;
            }
        }
        //Node<E> newNode = new Node(element,parent);
        Node<E> newNode = createNode(element,parent);
        if(cmp>0){
            parent.left = newNode;
        }else{
            parent.right = newNode;
        }
        size++;
        afterAdd(newNode);
    }

2.AVL树实现afterAdd(Node)

    @Override
    protected void afterAdd(Node<E> node) {
        
    }

1.明确失衡节点是谁,怎么修复

按照前面分析,失衡节点只会是node的祖先节点
只要修复了高度最低的失衡祖先节点,那么整个二叉树就平衡了

伪代码:

    protected void afterAdd(Node<E> node) {
        while((node = node.parent ) != null){
//            if(node 是平衡的){
//
//            }else{
//
//            }
        }
        //出来了,node = null
    }
2.1 判断平衡

根据节点的平衡因子来判断是否平衡;
平衡因子和节点的左右子树的高度有关系;
-> 因此可以对node类进行修改,在node中维护一个height属性
->但是只有在AVL树中需要height属性,普通BST不需要height属性
解决方法
在AVL树中自己维护一个Node类,继承BST中的Node

private static class AVLNode<E> extends Node<E>{
    private int height = 1; // 叶子节点的高度为1

    AVLNode(){}

    AVLNode(E element,Node<E> parent){
        this.element = element;
        this.parent = parent;
    }

问题2
在AVL树中维护了自己的AVLNode之后,但是add()方法是继承自BST的,那么就需要对创建Node进行一个封装,返回父类Node类型

	protected Node<E> createNode(E element,Node<E> parent){
        return new Node(element,parent);
    }

在AVLTree中,就要override此方法,返回AVLNode对象

    @Override
    protected Node<E> createNode(E element, Node<E> parent) {
        return new AVLNode<E>(element,parent);
    }

如此,在add()方法中,创建新的node的时候,就各自维护自己的node


AVLNode中添加有关平衡因子的方法
(1)由于left和right属性是从Node类继承过来的,因此这里需要进行强转才能使用height属性

//获取节点的平衡因子
    public int balanceFacotr(){
        int leftHeight = left==null? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right==null? 0 : ((AVLNode<E>)right).height;
        return leftHeight - rightHeight;
    }

在AVLTree中添加判断节点是否平衡的方法:

 private boolean isBalanced(Node<E> node){
        return Math.abs(((AVLNode<E>)node).balanceFacotr()) <= 1;
    }
2.2更新高度

在AVLNode中添加更新高度的方法:


    //更新节点的高度
    public void updateHeight(){
        int leftHeight =  left==null? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right==null? 0 : ((AVLNode<E>)right).height;
        height = Math.max(leftHeight,rightHeight) + 1;
    }

在AVLTree中:

   private void updateHeight(Node<E> node){
        ((AVLNode<E>)node).updateHeight();
    }

问题1:一个节点什么时候会被更新高度:

(1)只有添加的新节点node的祖先节点会更新高度;
(2)失衡节点g以及g的祖先节点不需要更新高度

因此如果要更新AVL树中节点的高度,需要从新节点遍历到g点,此间的每个节点需要更新高度

在伪代码中:

    protected void afterAdd(Node<E> node) {
        while((node = node.parent ) != null){
//            if(isBalanced(node)){
//
//            }else{
//
//            }
        }
        //出来了,node = null
    }

在获取平衡因子balanceFacotr()的时候是依赖左右子节点的高度的
我们需要沿着新节点的parent,挨个用isBalanced(node)判断祖先节点是否是平衡的,如果是平衡的,那就更新高度;如果不是平衡的,说明遇到g点了,此时就要做恢复平衡操作,并且g点往后不需要再更新高度了(参考:添加后修复总结的说明

protected void afterAdd(Node<E> node) {
        //沿着parent一直往上找到第一个失衡的父节点
       while((node=node.parent)!= null){
           //新节点的父节点肯定是平衡的,因此从这往上一直更新到
           //失衡祖宗节点的高度
           if(isBalanced(node)){
               updateHeight(node);
           }else{
               //这是第一个不平衡的节点
                rebalance(node);
                break;//只需要恢复第一个不平衡节点即可
           }
       }
    }
2.3恢复平衡

恢复平衡的时候,我们必须要知道是哪种场景;
以LL为例,我们会发现p是g的较高子节点;同理,n是p的较高子节点;
也就是说:

	1.获取g的较高子节点p,判断g和p的关系
	2.获取p的较高子节点n,判断n和p的关系

在这里插入图片描述
在BinaryTree的Node中添加两个辅助方法:

 public boolean isLeftChild(){ // 判断自己是不是左子树
            return parent!=null && this==parent.left;
        }

        public boolean isRightChild(){ // 判断自己是不是右子树
            return parent!=null && this==parent.right;
        }

在AVLNode中:

  //返回较高的子节点,如果一样高,返回同方向的子节点
    public Node<E> tallerChild(){
        int leftHeight = left==null? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right==null? 0 : ((AVLNode<E>)right).height;
        Node<E> ReNode = leftHeight > rightHeight? left : right;
        if(leftHeight == rightHeight){//返回相同方向的
            if(this.isLeftChild()){
                ReNode = left;
            }else{
                ReNode = right;
            }
        }
        return ReNode;
    }
}

balance方法:

//给Node恢复平衡  node是高度最低的失衡节点g
    private void rebalance(Node<E> node){
        //必须得获取n\p\g的关系,才能知道是四种失衡情况的哪一种
        //p是g左右子树中高度最高的节点
        //n是p左右子树中高度最高的节点
        Node<E> parent = ((AVLNode<E>)node).tallerChild();
        Node<E> n =  ((AVLNode<E>)parent).tallerChild();
        if(parent.isLeftChild()){
            if(n.isLeftChild()){
                //LL
                rotateRight(node);
            }else{
                //LR
                rotateLeft(parent);
                rotateRight(node);
            }
        }else{
            if(n.isLeftChild()){
                //RL
                rotateRight(parent);
                rotateLeft(node);
            }else{
                //RR
                rotateLeft(node);
            }
        }

    }

旋转操作:


    //左旋转
    protected void rotateLeft(Node<E> node) {
        Node<E> parent = node.right;
        Node<E> child =  parent.left;

        
        parent.left = node;
        node.right = child;
        
		//更新父子关系
        afterRotate(node,parent,child);
        
        //旋转之后,要更新grand和parent的高度
        updateHeight(node);
		updateHeight(p);
    }


    protected void rotateRight(Node<E> node){
        Node<E> parent = node.left;
        Node<E> child =  parent.right;

        parent.right = node;
        node.left = child;
		
		//更新父子关系
        afterRotate(node,parent,child);

		//旋转之后,要更新grand和parent的高度
        updateHeight(node);
        updateHeight(p);
    }

    protected void afterRotate(Node<E> grand,Node<E> parent,Node<E> child){
        //todo 1.更新parent的 父子关系
        parent.parent = grand.parent;
        if(grand.isLeftChild()){
            grand.parent.left = parent;
        }else if(grand.isRightChild()){
            grand.parent.right = parent;
        }else{
            root = parent;
        }
        //todo 2.更新child的父子关系
        if(child != null){
            child.parent = grand;
        }
        //todo 3.更新grand的父子关系
        grand.parent = parent;
    }
2.4恢复平衡的统一操作

在这里插入图片描述
g>f>e>d>c>b>a
四种情况,最终旋转完毕的结果都是一样的,d成为根节点
a和b的关系以及f和g的关系始终不变,因此a和g可以不处理。

private void rotate(
        Node<E> r, // 子树的根节点
        Node<E> b, Node<E> c,
        Node<E> d,
        Node<E> e, Node<E> f) {
    // 让d成为这颗子树的根结点
    d.parent = r.parent;
    if (r.isLeftChild()) {
        r.parent.left = d;
    } else if (r.isRightChild()) {
        r.parent.right = d;
    } else {
        root = d;
    }
    // b-c
    b.right = c;
    if (c != null) {
        c.parent = b;
    }
    updateHeight(b);

    // e-f
    f.left = e;
    if (e != null) {
        e.parent = f;
    }
    updateHeight(f);

    // b-d-f
    d.left = b;
    d.right = f;
    b.parent = d;
    f.parent = d;
    updateHeight(d);
}

注意:
只有左右子树改变的需要更新高度
更新高度的顺序必须是先更新低的节点


对应的修改rebalance方法:
在这里插入图片描述

4.AVL树删除操作

1.删除节点造成的失衡分析

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

  • 删除节点,导致直接父节点 或 祖先节点 失衡
  • 删除节点只会导致一个节点失衡
分析:
失衡节点在失衡前肯定是平衡的且平衡因子必须为1
因此删除节点后,该失衡节点的平衡因子变成了2
这意味着删除了该失衡节点的较短的一条腿
那么就不会影响失衡节点的高度
既然失衡节点高度不会变,那么失衡节点的祖先节点的高度就不会受到影响;
因此只会导致一个节点失衡;

2.删除导致失衡的恢复方案

1)LL

在这里插入图片描述

  • 恢复平衡的过程中,可能会引发连锁反应
    如图所示,删除的节点是红色的,当红色节点被删除,变成LL的情况,此时需要对g进行右旋转。
    如果绿色节点不存在,旋转之后,该子树的高度-1,这就有可能引发连锁反应。
    极端情况下被删除节点的所有祖先节点都需要进行恢复平衡的操作,共 O(logn) 次调整

说白了,对节点g进行rebalance操作,会导致以g为根节点的子树的高度降低1,这和删除一个节点造成的情况是一样的!

	因此在remove方法中找到被删除节点del之后,对del进行删除
	然后从del节点的parent开始找失衡节点,不管节点是否失衡,都需要更新高度
2)RR

在这里插入图片描述

3)LR

在这里插入图片描述

4)RL

在这里插入图片描述

3.删除代码实现

添加afterRemove(Node node)

由于节点的删除也是随机的,因此只能在删除操作完成之后,添加一个后处理方法,对树进行平衡恢复
1.方法添加位置
在节点被删除后;
这里需要注意:参数node是被删除的节点,对于度为2的节点的删除,删除的是其前驱节点或者后继节点。

    private void remove(Node<E> node){
        if(node == null) return;
        //先处理度为2的节点,因为也是删除度为1的节点
        // 找到要被删除的节点
        Node<E> del = null;
        if(node.left != null && node.right != null){
            del = predesessor(node);//前继节点
            node.element = del.element;
        }else{
            del = node;
        }

        Node<E> child = del.left == null ? del.right:del.left;
        if(child != null){
            child.parent = del.parent;
        }
        if(del.parent == null){
            root = child;
        }else{
            if(del == del.parent.left){
                del.parent.left = child;
            }else{
                del.parent.right = child;
            }
        }

        //节点真正被删除的时候删除
        afterRemove(del);
        size--;
    }

2.具体的实现

protected void afterRemove(Node<E> node) {
    //沿着parent一直往上找到第一个失衡的父节点
    while((node=node.parent)!= null){
        //新节点的父节点肯定是平衡的,因此从这往上一直更新到
        //失衡祖宗节点的高度
        if(isBalanced(node)){
            updateHeight(node);
        }else{
            //这是第一个不平衡的节点
            rebalance(node);
            //break;
        }
    }
}
  • 对比 afterAdd()只需要修复最低的失衡节点,因此在修复后break终止循环;而删除节点可能会引发多个祖父节点失衡,因此afterRemove()只需要去掉break即可。
  • 需要注意的是:在删除节点的过程中,我们并没有修改del节点的parent,因此node=node.parent是一直有效的。
  • 当节点是平衡的 只做高度的更新
  • 当节点是失衡的,做恢复平衡的操作(在afterRotate()中有对失衡节点的高度更新操作)
    所以:删除操作,del节点的所有祖先节点都需要更新高度 增加操作,高度更新只需要到失衡节点即可

AVL树总结

  • AVL树在二叉搜索树引入了平衡因子的概念;通过维护每个节点的平衡因子<1 从而实现二叉搜索树不会退化成链表;
  • AVL树核心操作是在BST的基础上增加了afterAdd()和afterRemove()两个操作,对增和删操作进行平衡修复

添加操作

失衡:添加导致g点失衡,g点的高度会改变,从而可能会导致g点所有祖先节点都失衡
     失衡节点不会是直接父节点
     
修复:修复后的g点高度和失衡前的g点高度是一样的,因此只要让高度最低的失衡节点恢复平衡,整棵树就恢复平衡
    【仅需 O(1) 次调整】
  • 删除
失衡:删除导致g点失衡,但是g的高度不会改变,从而只会造成一个节点失衡。
	 失衡节点可能是直接父节点或 祖先节点
	 
修复:g点恢复平衡可能会改变g点高度,因此恢复平衡后,可能会导致更高层的祖先节点失衡
    【最多需要 O(logn) 次调整】

平均时间复杂度

  • 搜索:O(logn)
  • 添加:O(logn),仅需 O(1) 次的旋转操作
  • 删除:O(logn),最多需要 O(logn) 次的旋转操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值