AVL 树小结

AVL 树是一种常见的平衡二叉搜索树

一、背景

1、问题

例如:在 n 个动态的整数中搜索某个整数是否存在?

0123456789
31661715282059884556

如果维护一个有序的动态数组,使用二分搜索,最坏时间复杂度:O(logn)

但是添加,删除的平均时间复杂度是O(n)

针对这个需求,有没有更好的方案?

使用二叉搜索树,添加,删除,搜索的最坏时间复杂度均可优化至:O(logn)

2、二叉搜索树

二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为 BST,有如下特点:

  • 任意一个节点的值都大于其左子树所有节点的值
  • 任意一个节点的都小于其右子树所有节点的值
  • 它的左右子树也是一颗二叉搜索树
  • 二叉搜索树存储的元素必须具备可比性,如果是自定义类型,需要指定比较方式,不允许为 null

根据二叉搜索树的定义,如果是按照  7,4,9,2,5,8,11 顺序添加可以构造一颗完全二叉树。

如果是从从小到大或者从大到小添加节点,二叉搜索树树将会退化成链表。此外,删除节点也有可能会导致二叉搜索树退化成链表。

如何避免二叉搜索树退化成链表呢?

首先,节点的添加,删除顺序是无法限制的,可以认为是随机的。所以,改进方案是:在节点添加,删除操作之后,想办法让二叉树恢复平衡。一颗达到适度平衡的二叉搜索树,可以称之为:平衡二叉搜索树,英文简称 BBST

二、AVL树简介

AVL树是最早发明的自平衡二叉搜索树之一,AVL 取名于两位发明者的名字(G.M.Adelson-Velsky 和 E.M.Landis)。

也有人把 AVL 树念做 “艾薇儿树”。

  • 每个节点的平衡因子只可能是 1,0,-1 (绝对值 <=1,如果超过1,称之为“失衡”)
  • 每个节点的左右子树高度差不超过1
  • 搜索,添加,删除的时间复杂度是O(logn)

 如下图所示:添加节点13之后,13的祖父节点14平衡因子为2,已经失衡了

 

三、添加导致的失衡

二叉搜索树是在叶子节点上添加节点

最坏情怀:可能会导致所有祖父节点都失衡

父节点,非祖先节点都不可能失衡,需要通过以下方式旋转恢复平衡

T0,T1,T2,T3 可能为空,长度和位置关系仅供参考,主要是需要关注旋转

 

1、LL - 右旋转(单旋)

如下图所示:添加红色节点后,g的平衡因子为2,失衡了

这属于在左子树的左子树上插入节点之后导致的失衡,通过右旋转 P 节点成为根节点,恢复平衡

节点大小关系如下:

n < p < T2 < g < T3

所以 p 节点右旋转成为根节点时,需要做以下调整:

  • g 成为 p 的右子树(p.right = g)
  • T2 大于 p,小于 g,T2之前是 p.right。所以 T2 成为 g 的左子树 (g.left = p.right)

此时整颗树达到了平衡,此外还需要注意一些收尾工作:例如:更新 T2,p,g 的parent 属性

调整之后如下所示:

部分实现代码如下: 

    private void rotateRight(Node<E> grand) {
		Node<E> parent = grand.left;
		Node<E> child = parent.right;
		grand.left = child;
		parent.right = grand;
		afterRotate(grand, parent, child);
	}

2、RR - 左旋转(单旋)

 如下图所示:添加红色节点后,g的平衡因子为 -2,失衡了

同理:这属于在右子树的右子树上插入节点之后导致的失衡,通过左旋转 P 节点成为根节点,恢复平衡

节点大小关系如下:

T0  < g < T1 <  p < n

所以 p 节点左旋转成为根节点时,需要做以下调整:

  • g 成为 p 的左子树(p.left = g)
  • T1 大于 g,小于 p,T1之前是 p.left。所以 T1 成为 g 的右子树 (g.right = p.left)

此时整颗树达到了平衡,此外还需要注意一些收尾工作:例如:更新 T1,p,g 的parent 属性

调整之后如下所示:

部分实现代码如下: 

    private void rotateLeft(Node<E> grand) {
		Node<E> parent = grand.right;
		Node<E> child = parent.left;
		grand.right = child;
		parent.left = grand;
		afterRotate(grand, parent, child);
	}

 3、LR - RR左旋转,LL右旋转(双旋)

如下图所示:添加红色节点后,g的平衡因子为 2,失衡了。

这属于在左子树的右子树上插入节点之后导致的失衡,此时需要先让 p 节点左旋转,然后让 g 节点右旋转

左旋和右旋和上面的操作一致,不再赘叙

旋转过程如下:

4、RL - LL右旋转, RR左旋转(双旋)

如下图所示:添加红色节点后,g的平衡因子为 2,失衡了。

这属于在右子树的左子树上插入节点之后导致的失衡,此时需要先让 p 节点右旋转,然后让 g 节点左旋转

旋转过程如下:

添加导致的失衡实现部分代码如下:

    private void rebalance(Node<E> grand) {
		Node<E> parent = ((AVLNode<E>)grand).tallerChild();
		Node<E> node = ((AVLNode<E>)parent).tallerChild();
		if (parent.isLeftChild()) { // L
			if (node.isLeftChild()) { // LL
				rotateRight(grand);
			} else { // LR
				rotateLeft(parent);
				rotateRight(grand);
			}
		} else { // R
			if (node.isLeftChild()) { // RL
				rotateRight(parent);
				rotateLeft(grand);
			} else { // RR
				rotateLeft(grand);
			}
		}
	}

四、删除导致的失衡

作为一颗二叉树搜索树,AVL 树的删除跟二叉树搜索树的删除一致

叶子节点度为 1 的节点度为 2 的节点
直接删除使用子节点替代原节点的位置先用前驱或者后继节点的值覆盖原节点的值,然后删除相应的前驱或者后继节点

以下我们只讨论度为 2 的节点的删除,如下图所示:删除子树中的16

可能会导致 父节点祖先节点 失衡,其他节点都不会失衡

同理,删除导致的失衡也是通过旋转回复平衡

如下图所示:删除红框节点

如果没有 绿色框节点,p右旋之后,右边的子树高比左边的子树高小1。

可能会导致 p 节点的父节点或更高层节点失衡。

极端情况下,所有祖先节点都需要进行恢复平衡的操作,共 O(logn)次调整。

五、总结

 添加删除搜索
时间复杂度O(logn)O(logn)O(logn)
失衡调整可能会导致所有祖先节点都失衡,只要让高度最低的失衡节点恢复平衡,整颗树就平衡了,仅需要O(1)次调整可能会导致父节点或祖父节点失衡(只有一个节点会失衡),恢复平衡后可能会导致更高层的祖先节点失衡,最多需要 O(logn) 次调整 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值