AVL树
普通的二叉搜索树的退化
在使用二叉搜索树时,只规定了左子树的值小于根节点的值,右子树的值大于根节点的值,并没有对树的形状做出要求。
在树较为平衡(左右子树高度相差较小)时,如下图所示:
我们在二叉搜索树上进行的增加、删除、查找的操作的时间复杂度平均为 O ( l o g   n ) O(log\,n) O(logn)。
但是在树比较倾斜时,在二叉搜索树上的操作的时间复杂度也会随之增加,树越倾斜,退化也就越严重,时间复杂度也就越高。
一种极端情况下,当所有的节点(除叶节点外)都只有一个子树或子节点时,该树结构会退化为链表,相应的时间复杂度也会退化为 O ( n ) O(n) O(n)。
为了解决这个问题,平衡树也就应运而生。其中最早的平衡树便是本文的重点——AVL树。
什么是AVL树
AVL树是最早被发明的自平衡二叉搜索树。它的名字不是自平衡二叉搜索树的缩写,而是来源于它的发明者G. M. Adelson-Velsky
和Evgenii Landis
。
AVL树在二叉搜索树的性质上增加了一条——任一节点的两个子树高度差最大为1。因此,AVL树在执行增加、删除、查找操作的时间复杂度在平均、最坏情况下均为 O ( l o g   n ) O(log\,n) O(logn)。不会出现二叉搜索树中退化的情况。
平衡因子
AVL树中使用这个参数来判定当前节点是否是平衡的。平衡因子由当前节点的左子树高度减去右子树高度得到。
所以当一个节点的平衡因子大于1或小于-1时,则可以判定当前节点不平衡,需要进行调整。
另外,AVL树中也可以不保存节点的平衡因子,而保存树的高度,在需要时计算得到。
旋转
旋转是一种改变树的平衡性的操作,几乎所有的平衡树都是通过旋转操作来调整树的结构,使树保持平衡状态。
旋转分为两类:
- 左旋转
- 右旋转
左旋转
总共3步:
- 将根节点的右子节点设置为右子节点的左子节点。
- 将右子节点的左子节点设置为根节点。
- 将右子节点设置为根节点。
相应的代码为:
AVLTreeNode* RotateL(AVLTreeNode* root) {
AVLTreeNode* temp = root->right;
root->right = temp->left;
temp->left = root;
return temp;
}
旋转前后对比:
从图中可以看出,左旋转之后,相比于原来的树,左子树高度增加了1,右子树高度减少了1。
右旋转
类似于左旋转,也为三步:
- 将根节点的左子节点设置为左子节点的右子节点。
- 将左子节点的右子节点设置为根节点。
- 将左子节点设置为根节点。
相应的代码为:
AVLTreeNode* RotateR(AVLTreeNode* root) {
AVLTreeNode* temp = root->left;
root->left = temp->right;
temp->right = root;
return temp;
}
旋转前后对比:
从图中可以看出,右旋转之后,相比于原来的树,右子树高度增加了1,左子树高度减少了1。
AVL树中不平衡情况及对应旋转操作
在操作AVL树过程中,总共会遇到4种不平衡的情况:LL、RR、LR、RL。
LL情况——右旋转
LL
情况是指根节点的左子节点的左子树高度比右子树高度大2的情况。即:
图中A
节点为根节点。
进行一次右旋转即可使树重新平衡。
RR情况——左旋转
RR
情况是指根节点的右子节点的右子树高度比左子树高度大2的情况。即:
图中A
节点为根节点。
进行一次左旋转即可使树重新平衡。
LR情况——先左旋转,再右旋转
LR
情况是指根节点的左子节点的右子树比左子树高度大2的情况。即:
图中A
节点为根节点。
此时需进行两次旋转:
- 先以左子节点(
B
)为根进行一次左旋转。(将情况转化为LL
情况) - 再以
A
节点为根进行一次右旋转。
RL情况——先右旋转,再左旋转
RL
情况是指根节点的右子节点的左子树比右子树高度大2的情况。即:
图中A
节点为根节点。
此时需进行两次旋转:
- 先以右子节点(
B
)为根进行一次右旋转。(将情况转化为RR
情况) - 再以
A
节点为根进行一次左旋转。
AVL树的操作
在AVL树上操作主要有三种:增加、删除、查找。
三种操作与二叉搜索树上对应的三种操作基本相同,只是增加、删除操作有可能会引起树的不平衡,需要进行额外调整。
为便于说明,此处先对AVL树结构进行说明:
class AVLTree {
private:
class AVLTreeNode {
public:
int val; /// 当前节点所存的数值
int height