在前面的文章中,我们学习了二叉搜索树,也了解了二叉搜索树的底层原理,可是思考这样一个问题:当按照有序顺序往树中插入元素时,搜索二叉树就变成了如图所示的单支树:
而这棵树一经旋转,似乎又变成链表了呀!原本希望使用二叉搜索树能够增加效率,可这样来看,二叉搜索树似乎也并不好用了。有什么办法能够解决这个问题呢?——AVL树就为解决这一问题提供了方法。
🥇AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
即AVL树要么是一棵空树,要么具有以下性质:
①:它的左右子树都是AVL树;
②:左右子树高度差(简称平衡因子)的绝对值不超过1(-1/0/1)。
🥇AVL树的实现
🥈AVL树的节点
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left; //节点的左孩子
AVLTreeNode<K, V>* _right; //节点的右孩子
AVLTreeNode<K, V>* _parent;//节点的双亲
int _bf; //节点的平衡因子
pair<K, V> _kv;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
}
🥈AVL树的插入
我们知道,AVL树就是一棵特殊的二叉搜索树,不过是在二叉搜索树的基础上引入了平衡因子,所以AVL树的插入过程可以分为两步:
1.按照二叉搜索树的方式插入新节点;
2.调整平衡因子。
插入新节点很容易,我们直接参照数据结构9——二叉搜索树中的代码即可,可是如何调整平衡因子呢?
①:根据上图的列举的AVL树,根据图中的平衡因子计算规则,我们可以规定:如果新插入的节点是左孩子,那么平衡因子_bf--,如果新插入的节点是右孩子,那么平衡因子_bf++;
②:还需要注意的是,新插入孩子节点之后,其不单单会影响父亲的平衡因子,还会影响部分祖先的平衡因子,此时就要分情况讨论:有可能不会影响祖先的平衡因子,此时就无需再加额外操作(如下图1所示);也有可能会造成祖先的平衡因子的绝对值大于1,此时就要进行旋转处理(如下图2所示)。
具体代码如下:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent)
{
//更新双亲的平衡因子
if (cur == parent->left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//更新后检测双亲的平衡因子
//①更新后,parent->_bf == 0,parent所在子树高度不变,不会影响
//祖先节点,说明更新前parent的_bf是1或-1,parent缺孩子的那边插入
//了节点,左右均衡了,parent的高度不变,不会影响祖先,此时更新直接结束
if (parent->_bf == 0)
{
break;
}
//②更新后,parent->_bf == 1/-1,p所在的子树高度发生变化,会影响
//祖先节点,说明更新前parent的_bf是0,parent有一边插入了节点,
//parent发生变化,虽然parent的_bf符合规定,可其祖先的_bf可能会不符合规定
//所以需要继续向上检查爷爷节点、太爷爷节点、曾爷爷节点......
else if (parent->_bf == 1 || parent->_bf == -1)
{
//插入前双亲的平衡因子是0,插入后双亲的平衡因子为1或-1,
//说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整
cur = cur->_parent;
parent = parent->_parent;
}
//③检查到有祖先节点的_bf变为了2/-2,此时不符合AVL树的规定,需要进行旋转处理
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转处理
}
else
{
// 插入之前AVL树就有问题
assert(false);
}
}
return true;
}
🥈AVL树的旋转
在上面的插入过程中,有可能会造成AVL树不平衡的结果,此时就要调整树的结构,使之平衡化,根据插入节点位置的不同,AVL树的旋转可分为四种:右单旋、左单旋、先左单旋再右单旋、先右单旋再左单旋。
🥉右单旋
右单旋是指新插入节点在较高左子树的左侧。
右单旋的基本方法是(以下图树为例):将15变成30的左孩子,再将30变成20的右孩子。
具体代码如下:
//①右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;// subL: parent的左孩子
Node* subLR = subL->_right;// subLR: parent的左孩子的右孩子(可能为空)
//①subLR变为parent的左孩子
parent->_left = subLR;
//②subLR可能不存在,如果存在,则更新subLR的父亲:subLR的父亲从subL变为parent
if (subLR)
{
subLR->_parent = parent;
}
//③parent从subL的父亲变为subL的右孩子
subL->_right = parent;
//④由于parent可能是棵子树,因此在更新父亲之前必须先保存parent的父亲
Node* pparent = parent->_parent;
//⑤更新parent的父亲:subL从parent儿子的身份变为parent的父亲
parent->_parent = subL;
//⑥如果parent是根节点,更新指向根节点的指针
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
//如果parent是子树,其可能是父亲的左子树,也可能是右子树
if (pparent->_left == parent)
{
pparent->_left = subL;
}
else
{
pparent->_right = subL;
}
//⑦更新subL的父亲:subL的父亲从parent变为pparent
subL->_parent = pparent;
}
subL->_bf = 0;
parent->_bf = 0;
}
🥉左单旋
右单旋是指新插入节点在较高右子树的右侧。
左单旋的基本方法是(以下图树为例):将35变成30的右孩子,再将30变成40的左孩子。
具体代码如下:
//②左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;// subR: parent的右孩子
Node* subRL = subR->_left;// subRL: parent的右孩子的左孩子(可能为空)
//①subRL变为parent的右孩子
parent->_right = subRL;
//②subRL可能不存在,如果存在,则更新subRL的父亲:subRL的父亲从subR变为parent
if (subRL)
{
subRL->_parent = parent;
}
//③parent从subR的父亲变为subR的左孩子
subR->_left = parent;
//④由于parent可能是棵子树,因此在更新父亲之前必须先保存parent的父亲
Node* pparent = parent->_parent;
//⑤更新parent的父亲:subR从parent儿子的身份变为parent的父亲
parent->_parent = subR;
//⑥如果parent是根节点,更新指向根节点的指针
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
//如果parent是子树,其可能是父亲的左子树,也可能是右子树
if (pparent->_left == parent)
{
pparent->_left = subR;
}
else
{
pparent->_right = subR;
}
//⑦更新subR的父亲:subR的父亲从parent变为pparent
subR->_parent = pparent;
}
//⑧旋转结束,更新平衡因子
parent->_bf = 0;
subR->_bf = 0;
}
🥉左右双旋
左右双旋即先左单旋再右单旋,是指新插入节点在较高左子树的右侧。
左右双旋的基本方法是(以下图树为例):先以30为根进行左单旋,再以40为根进行右单旋。
具体代码如下:
//③左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = subLR->_bf;
//①先以parent的左孩子为根进行左单旋
RotateL(parent->_left);
//②再以parent为根进行右单旋
RotateR(parent);
//③调整平衡因子
if (bf == -1)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subLR->_bf = 0;
subL->_bf = -1;
parent->_bf = 0;
}
else if (bf == 0)
{
subLR->_bf = 0;
subL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
🥉右左双旋
右左双旋即先右单旋再左单旋,是指新插入节点在较高右子树的左侧。
右左双旋的基本方法是(以下图树为例):先以40为根进行右单旋,再以30为根进行左单旋。
具体代码如下:
//④右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
int bf = subRL->_bf;
//①先以parent的右孩子为根进行右单旋
RotateR(subR);
//②再以parent为根进行左单旋
RotateL(parent);
//③调整平衡因子
subRL->_bf = 0;
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
}
else
{
parent->_bf = 0;
subR->_bf = 0;
}
}
此时完善Insert函数:
//③检查到有祖先节点的_bf变为了2/-2,此时不符合AVL树的规定,需要进行旋转处理
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转处理
if (parent->_bf == 2 && cur->_bf == 1)//左单旋
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)//右单旋
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
{
RotateLR(parent);
}
else//右左双旋
{
RotateRL(parent);
}
break;
}
🥇AVL树的验证
AVL树需要满足:①二叉搜索树和②平衡性
bool _IsBalance(Node* root, int& height)
{
if (root == nullptr)
{
height = 0;
return true;
}
int leftHeight = 0, rightHeight = 0;
if (!_IsBalance(root->_left, leftHeight)|| !_IsBalance(root->_right, rightHeight))
{
return false;
}
if (abs(rightHeight - leftHeight) >= 2)
{
cout << root->_kv.first << "不平衡" << endl;
return false;
}
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
return true;
}
bool IsBalance()
{
int height = 0;
return _IsBalance(_root, height);
}
🥇测试
void TestAVLTree()
{
//int a[] = { 30, 23, 17, 11, 3, 20, 18, 14, 15 };
int a[] = { 14, 21, 16, 11, 13, 5, 15, 7, 6, 14 };
AVLTree<int, int> t;
for (auto e : a)
{
if (e == 14)
{
int x = 0;
}
t.Insert(make_pair(e, e));
//看是插入谁导致出现的问题
cout << e << "->" << t.IsBalance() << endl;
}
t.InOrder();
cout << t.IsBalance() << endl;
}
int main()
{
TestAVLTree();
return 0;
}
结果如图: