数据结构之AVL树(C++)
AVL树是二叉搜索平衡树,在二叉搜索树的基础上添加了平衡,也就是确保每个结点的左右子树高度差的绝对值不超过1。
我之前的博客已经介绍过了二叉搜索树的基本概念和简单实现,具体参考数据结构之二叉搜索树(C++实现)。
1 AVL树的概念
我们知道二叉搜索树虽然可以缩短查找的效率,但是数据有序或者接近有序的情况下,二叉搜索树回退化成单支树,查找元素相当于在顺序表中遍历查找,效率低下。
AVL树是一种二叉搜索平衡树,它可以是一颗空树或者满足以下性质的二叉搜索树:
- 左右子树高度之差(平衡因子)的绝对值不超过1。
- 它的左右子树的都是AVL树。
2. AVL树的实现
2.1 AVL树的定义
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left; // 左孩子
AVLTreeNode<K, V>* _right; // 右孩子
AVLTreeNode<K, V>* _parent; // 双亲
pair<K, V> _kv; // 存储键值对
int _bf; // 平衡因子
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
2.2 AVL树的插入
AVL树的插入是在二叉搜索树的基础上引入了平衡因子,插入过程分为两步:
-
按照二叉搜索树插入新节点。
-
调节结点的平衡因子。
重点介绍二叉平衡搜索树如何进行平衡因子调节,在插入之前,父节点的平衡因子有三种情况:-1,0,1,分一下两种情况:
- 如果cur插入到parent的左侧,只需要给parent的平衡因子-1即可。
- 如果cur插入到parent的右侧,只需要给parent的平衡因子+1即可。
此时,parent平衡因子可能有三种情况,0,正负1,正负2。
- 如果parent平衡因子为0,说明之前的平衡因子为正负1,调整之后依然满足AVL树的特性。
- 如果parent平衡因子为正负1,说明插入之前的平衡因子为0,插入之后,会导致以parent为根的数高度增加,那么会影响parent的祖先结点的平衡因子,因此,需要继续向上更新,直到
parent==root
。 - 如果平衡因子为正负2,已经不满足平衡树的性质,需要做旋转处理。
AVL树针对不同情况,需要做的旋转也不同,主要是分为四种。
-
新节点插入较高左子树的左侧,执行右单旋。
void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) { subLR->_parent = parent; } Node* ppNode = parent->_parent; subL->_right = parent; parent->_parent = subL; if (_root == parent) { _root = subL; subL->_parent = nullptr; } else { if (ppNode->_left == _parent) { ppNode->_left = subL; } else { ppNode->_right = subL; } subL->_parent = ppNode; } subL->_bf = parent->_bf = 0; }
-
新节点插入较高右子树的右侧,执行左单旋。
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppnode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
parent->_bf = subR->_bf = 0;
}
- 新节点插入较高左子树的右侧,先执行左单旋,再右单旋。
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 0;
subL->_bf = 1;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
-
新节点插入较高右子树的左侧,先执行右单旋,在左单旋。(参考左右双旋)
void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); RotateL(parent); subRL->_bf = 0; if (bf == 1) { subR->_bf = 0; parent->_bf = -1; } else if (bf == -1) { subR->_bf = 1; parent->_bf = 0; } else if (bf == 0) { parent->_bf = 0; subR->_bf = 0; } else { assert(false); } }
插入代码如下:
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;
// 控制平衡
// 1、更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;
}
else if (abs(parent->_bf) == 1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(parent->_bf) == 2)
{
// 说明parent所在子树已经不平衡了,需要旋转处理
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 if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
2.3 AVL树的验证
AVL树是在二叉搜索树的基础上做了平衡性的调整,因此,我们需要对二叉搜索树进行平衡判断。可以分为两步:
- 验证是否为二叉搜索树。如果中序遍历得到的是一个有序的值,那么说明是二叉搜索树。
- 验证是否为平衡树。每个结点的子树高度差是否不超过1或者判断每个结点的平衡因子是否是-1、1、0这三种值。
// 1. 中序遍历
void Inorder()
{
_InOrder(_root);
cout << endl;
}
// 2. 平衡判断
bool IsBalance()
{
_IsBalance(_root);
}
bool _IsBalance(Node *root)
{
// 空树满足AVL树
if(root == nullptr)
return true;
// 左子树高度
int leftHT = Height(root->_left);
// 右子树高度
int rightHT = Height(root->_right);
// 左右子树高度差
int diff = rightHT - leftHT;
// 左右子树的高度差不等于平衡因子
if(diff != root->_bf) {
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
// 递归调用,如果平衡因子的绝对值小于2并且左子树和右子树都是AVL树。
return abs(diff) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}
int Height(Node* root)
{
if(root == nullptr)
return 0;
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
return max(leftHT,rightHT) + 1;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
2.4 AVL树的删除(了解)
AVL树也是一种二叉搜索树,按照二叉搜索树的方式将结点删除,然后更新平衡因子,具体可以参考《算法导论》或者《数据结构-用面向对象方式与C++描述》这两本书。
3. AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log_2 (n)
。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。