一、前言
- AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家,他们在1962 年的论文《Analgorithmfortheorganizationofinformation》中发表了它。
- AVL树实现这⾥我们引入一个平衡因子(balancefactor)的概念,每个结点都有一个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1, AVL树并不是必须要有平衡因子,但是有了平衡因子可以更⽅便我们去进行观察和控制树是否平衡, 就像一个风向标一样。
1.1、AVL名字的由来
AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家。
1.2、AVL树与二叉搜索树的区别
AVL树是最先发明的平衡二叉搜索树,AVL是一棵空树或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一棵高度平衡搜索二叉树, 通过控制高度差去控制平衡。
1.3、深入思考
想一想为什么AVL树的实现要求是两棵子树的高度差不超过一,为甚么不要求子树的高度差为0呢,命名高度差为0更平衡?
答:实际上高度差为0当然可以更加平衡,但是并不是所有的二叉搜索树的子树的高度差都可以为0,像下面的几棵AVL树就不可能实现高度差为0。
二、模拟实现AVL树
2.1、何为平衡因子
任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1。 记住一点就是平衡因子并不是AVL树所必须的,但是有了平衡因子可以方便实现和控制AVL树。
2.2、实现AVL树的核心操作(控制平衡因子和旋转)
2.2.1、控制平衡因子的更新
- 第一点:AVL树作为一棵平衡树,实现它的关键是保证平衡,我们采用平衡因子来控制AVL树的平衡,如何计算平衡因子呢,我们子需要记住一个公式就是使用平衡因子 = 右子树的高度 - 左子树的高度,也就是说如果我们能够保证这棵搜索树中的所有结点的平衡因子都是能满足在[-1, 1]这个区间,那么它就是AVL树。
- 第二点:我们已经知道了平衡因子就是控制树平衡的关键,那么我们要如何控制平衡因子呢?其实很简单,上面我们已经只知道了平衡因子的计算公式就是右子树的高度 - 左子树的高度,所以对于任意一个结点的平衡因子的计算就是++或者--。然后在我们插入完成结点后开始从插入的位置向上更新平衡因子即可,由于插入结点只会影响插入的那边子树的平衡因子并不会影响另一边子树的平衡因子所以最多就是更新到根结点就结束了。所以我们仅需要一个while循环以父结点作为循环的条件,当父结点为空时就是结束的时候。
下面我们来看几种更新平衡因子的情况:
1.第一种情况:
分析:这是最简单的一中情况就是在更新平衡因子的过程中,如果更新到当前结点的父结点的平衡因子为零,那么就不需要进行继续更新了,直接break就好了。
为什么在调整平衡因子的过程中当平衡因子变成0后就不需要继续向上进行调整了?
答:平衡因子变成0根本原因就是插入结点并没有导致子树的高度发生变化。很容易理解原来子树的两棵子树存在高度差,插入一个结点后使得高度差消失了。
2.第二种情况:
分析:第二种情况也是较为常见和好想到的,平衡因子的更新一直更新到了根结点这种情况。就是一直在循环中更新parent结点的平衡因子以及cur和parent的指向。
3.第三种情况:
分析:第三种情况就是比较较为复杂的情况了就是更新的过程中发现平衡因子出去了[-1, 1]这个区间变成了2或者-2,如果遇到这种情况,也就没有必要继续进行更新平衡因子了,因为此时这棵树已经不平衡了,需要进行调整,直到调整到平衡,这个调整的操作便称为旋转。
插入一个结点13后重新更新平衡因子发现跟新到10的时候平衡因子变成了2,这时候便要停止更新进行旋转操作当旋转完后就可以直接进行break,因为旋转不只是可以控制平衡还可以控制树高度。
2.2.2、单旋旋转操作
概述:单旋操作就是将parent结点压下来将subL或者subR变成根结点。下面详细讲解。
旋转的规则:第一点:保持搜索树的规则。
第二点:让旋转的树从不平衡变平衡,其次降低树的高度。(将树的高度变成原来的高度)
2.2.2.1、右单旋
给大家介绍一下需要进行右单旋的情况如下图:
- 图一是一棵平衡的AVL树,我们对其的a子树插入一个结点(图二)导致a子树的高度由开始的h变成h + 1,在这种情况下调整这棵树的平衡因子发现最后无法导致这棵树变得平衡,所以要进行旋转操作,针对于这种情况进行的旋转称为右单旋。
下面来介绍一下右单旋需要进行的操作有哪些?
如图为旋转后的AVL树。我们来分析一下如何进行操作的:定义三个结点,分别为parent,subL,subLR,图中的10就是parent结点,5就是subL结点,5的右孩子就是subLR结点然后按照图中的变化规律进行更新,即将subL的右孩子也就是subLR给到parent的左边,然后将parent给到subL的右边。这个更新的过程要注意下面几点,就是subLR可能为空,不要忘记了判断,还有就是不要忘记更新结点的_parent成员。
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* ppnode = parent->_parent;
// 更新b子树
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
// 更新parent
subL->_right = parent;
parent->_parent = subL;
// 判断ppnode是否为空
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
parent->_bf = subL->_bf = 0;
}
2.2.2.2、左单旋
给大家介绍一下需要进行左单旋的情况如下图:
如果出现图中的这种情况,就要进行左单旋操作:
我们来分析一下左单旋是如何进行操作的:和有单旋是一样的先定义三个结点,分别为parent,subR,subRL,图中的10就是parent结点,15就是subR结点,15的左孩子就是subRL结点然后按照图中的变化规律进行更新,即将subR的左孩子也就是subRL给到parent的右边,然后将parent给到subR的左边。这个更新的过程要注意下面几点,就是subRL可能为空,不要忘记了判断,还有就是不要忘记更新结点的_parent成员。
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* ppnode = parent->_parent;
// 更新b子树
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
// 更新parent
subR->_left = parent;
parent->_parent = subR;
// 判断ppnode是否为根结点
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
parent->_bf = subR->_bf = 0;
}
2.2.3、双旋转操作
还有两种情况单纯的单旋操作时无法解决问题的,这时候就需要进行双旋操作了。上面的单旋操作就是将parent结点压下来将subL或者subR变成根结点,而双旋操作就是将parent压下来subLR或者subRL变成根节点。
2.2.3.1、左右双旋
这种情况需要注意的就是对于平衡因子的更新。
看一下图中的这种插入情况,这种情况和上面的情况进行相比的话就是不是纯粹的左边高或者右边高,我将它称为,左边高的右边高,这种情况需要进行左右双旋。先对5结点进行左旋,然后再对10结点进行右旋。依旧是和上面一样的命名方式,所以这里我们知识需要复用一下上面的单旋的代码,需要注意是我们要将b子树分开记录一下subLR的平衡因子方便后续进行更新平衡因子。
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else {
assert(false);
}
}
2.2.3.2、右左双旋
右左双旋和左右双旋的逻辑是一样的,唯一的区别就是旋转的方向发生了变化,这里不做过多的赘述了,直接上代码。
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else {
assert(false);
}
}
2.3、AVL树的插入操作
bool insert(const pair<K, V>& kv)
{
// 1.插入结点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (cur->_kv.first < parent->_kv.first) parent->_left = cur;
else parent->_right = cur;
// 更新cur结点的_parent成员
cur->_parent = parent;
// 2.调整平衡因子
while (parent)
{
// 更新
// 公式:_bf = 右子树的_bf - 左子树的_bf
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
// 2.1 _bf == 0
if (parent->_bf == 0)
{
break;
}
// 2.2 _bf == 1 || _bf == -1
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
// 2.3 _bf == 2 || _bf == -2
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 已经不平衡进行旋转
// 3.1 右单旋
if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
// 3.2 左单旋
else if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
// 3.3 左右双旋
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
// 3.4 右左双旋
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
// 旋转完成直接break
break;
}
// 2.4 其他情况
else
{
//不可能走到这里,如果走到直接断言报错
assert(false);
}
}
return true;
}
2.4、判断AVL树是否平衡的函数实现
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{
if (nullptr == root)
return true;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int diff = rightHeight - leftHeight;
if (abs(diff) >= 2)
{
cout << root->_kv.first << "高度差异常" << endl;
return false;
}
if (root->_bf != diff)
{
cout << root->_kv.first << "平衡因⼦异常" << endl;
return false;
}
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
注意上面的两个函数都是递归函数,在实现时我们通常给它套一层壳子进行调用。