平衡二叉树(AVL)
1、为什么会出现平衡二叉树
1.1、什么是二叉树
二叉树特点是每个结点最多只能有两棵子树,且有左右之分。
性质:
- 二叉树的第i层上至多有2 ^ (i - 1)(i≥1)个节点
- 深度为h的二叉树中至多含有2^h - 1 个节点
- 若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0 = n2+1
- 具有n个节点的完全二叉树深为 log2^x + 1(其中x表示不大于n的最大整数)
1.2、什么二叉查找树
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树
性质:
-
若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。
-
若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。
-
任意结点的左、右子树也分别为二叉搜索树 。
缺点:
插入1,2,3,4,5这几个数,生成的二叉搜索树为:
查找的速度并不可以得到提高还为o(logn),所以引出来了我们今天要讲解的平衡二叉树。
平衡二叉树(Balanced Binary Tree),又称AVL树,指的是左子树上的所有节点的值都比根节点的值小,而右子树上的所有节点的值都比根节点的值大,且左子树与右子树的高度差最大为1。
平衡二叉树性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
知道了平衡二叉树的概念之后,我们来聊聊为什么会出现这种数据结构?
之前我们已经介绍过了二叉树,我们我们知道了二叉树有一定的缺点,不如我们在查找的时候必须把全部的节点全部遍历一遍,但是平衡二叉树会缩小范围,因为左边的都比根小,右边的都比跟大。
平衡因子(BF):节点的左右子树的高度和高度差。根据平衡二叉树的概念可知,平衡因子的取值只能为【-1,0,1】(左-右 或者 右-左 都可以,下面采取的为 右-左)
2、平衡二叉树的插入操作
-
插入前是一个 AVL + 搜索特性 的二叉树
-
先不考虑插入后是否满足特性,不满足时会进行修复
-
插入完毕后,得到的还应该是一个满足 AVL + 搜索的二叉树
定义节点:
class Node<K,V> {
private K key;
private V value; // K-V 模型带着 value ,纯 K 模型,可以不带 value
Node<K,V> left;
Node<K,V> right;
int bf; // 平衡因子
Node<K,V> parent; // 保存自己的双亲节点,如果是根,则是null
}
先定义原来的树
2.1、按照普通搜索树的方式进行插入
-
如果 key 已经存在,则放弃插入
-
通过查找,找到 4 理论上应该在的位置
-
把 4 插入到理论的位置,并且调正平衡因子
2.2、设置 + 修改平衡因子
-
cur 的平衡因子一定是 0
-
思考谁的平衡因子需要修改? 答案是parent
- 如果 cur 是parent 的右孩子,parent.bf = parent.bf + 1;
- 如果 cur 是parent 的左孩子,parent.bf = parent.bf - 1;
- parent.bf 被修改后的值可能是多少?【-2,-1,0,1,2】
下面我们就根据parent.bf的取值分类 讨论下怎么调整可以使这棵树,使它 满足 平衡二叉树的条件!
1、被修改后的bf = 0
则可以得出 H(parent) 这棵树的高度没有变化
因为 H(parent) 没有变,所以 不会影响 parent.parent 的平衡因子
所以,插入过程结束后(整棵树的 bf 都是正确的,并且满足 AVL 树的特性)
举例:
2、被修改后的bf=1or-1
这个结果说明H(parent)增加了 1
因为 parent 的高度变了(增加了 1 ),所以 parent.parent 的平衡因子也需要调整
只要 bf 调节完的结果是 -1/1 ,则调整 bf 的过程会向上蔓延,直到遇到 0 或者 遇到 -2/2 并进行修复 或者遇到 root
2、被修改后的bf=2or-2
需要利用规则进行修复,使得 继续满足 AVL 树的特性
在讲这个规则之前,我们先来思考一个问题:
在修复之前,例如 parent.bf = 2,问其他位置的 bf 有可能也会破坏性质吗?
答案是不会的。任意时刻,一个 AVL树 最多只会有一个节点的 BF 不满足性质
AVL 把 破坏规则的情况叫做失衡(失去平衡)
失衡分为四种:
左左失衡 / 左右失衡 / 右右失衡 / 右左失衡
1、左左失衡:parent 的失衡是因为在 parent 的左孩子 的左子树中插入导致的失衡
2、左右失衡: parent 的失衡 是因为在 parent 的左孩子的右子树中插入导致的失衡
3、右右失衡:parent 的失衡是因为在 parent 的右孩子的右子树中插入导致失衡
右左失衡:parent 的失衡是因为 在parent的右孩子的左子树插入导致的失衡
我们已经了解了几种失衡了,那么怎么解决呢失衡问题呢?
1、左左失衡:只需要对 parent(失衡的节点)进行右旋即可
举例:
证明为什么右旋可以解决左左失衡问题?
根据搜索的性质,可以得出:
K(甲) < K(B) < K(乙) < K(A) < K(丙)
我们假设 H(丙) = X,并且 BF(A) = -2
我们可以得出 H(B) = X+1
思考:H(乙) 可以为 X+1 吗?答案是不可以,因为如果 H(乙) = X+1 的话,就代表还没插入节点就已经失衡了,并不是因为插入了节点才失衡了,所以 只能 H(甲) = X + 1
思考:H(乙) 可以为 X-1或者更小 吗?答案是不可以,因为如果 H(乙) = X - 1 的话,就是 B 这个节点是不平衡,就轮不到 解决 A 的失衡问题了
修复:
-
插入前 H(A) = X + 2,插入+旋转后 H(B) = X + 2
-
修复后:满足:
- 修复后是否仍然满足搜索树的特征
- 修复后是否仍然满足AVL的特征
- 修复后,树的整体是不是不变
2、左右失衡:先对 cur 进行左旋,再对 parent 进行右旋
证明:
根据搜索的性质,可以得出:
K(甲) < K(B) < K(Z) < K© < K(丙) < K(A) < K(丁)
假设:H(丁) = X ,那么 H(B) = X + 2
则,H(甲) 一定 X ,原因同上
H© 一定为 X + 1
下面我们来看看 H(乙) 和 H(丙)
1、首先他们之间有一个H为 X
2、如果插入的节点在乙子树中,则插入后 H(乙) = X,H(丙) = X - 1;
如果插入的节点在丙子树中,则插入后 H(丙) = X,H(乙) = X - 1;
这里面的原因可以利用反证法来证明。把前面左左失衡理解了,这里和左左失衡的道理是一样的
修复后,满足:
- 修复后是否仍然满足搜索树的特征
- 修复后是否仍然满足AVL的特征
- 修复后,树的整体是不是不变
3、右右失衡和右左失衡和前面的刚好相反,这里只介绍一下它怎么旋转,具体的证明和前面的类似
右右失衡:parent 左旋
右左失衡: 先 cur 右旋,再 parent 左旋
总结插入过程:
1、按照普通搜索树的方式插入
2、插入之后,调整 parent.bf :插入到左边 bf --,插入右边 bf ++
bf 最后变为 3 类值
0 :插入结束
-1/1:调整 bf 的过程向上蔓延(遇到 0 | -2 / 2 | 根节点 停止)
-2/2:进行修复,不会向上蔓延
3、针对失衡的情况进行修复
4、插入结束之后,是一颗 AVL 树
失衡分为四种情况: parent 是失衡的节点,cur 是 parent 的孩子(新节点所在子树的根)
左左失衡:parent右旋
左右失衡:先 cur 左旋,再 parent 右旋
右右失衡:parent 左旋
右左失衡: 先 cur 右旋,再 parent 左旋