前面对map/multimap/set/multiset进行了简单的介绍,其中我们发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
1、AVL树概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均 搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O(log_2 n),搜索时间复杂度O(log_2 n)。
2、AVL树节点结构解析
设计思路:
每个节点存储键值对,用于排序和查找
包含左右子节点指针实现树结构
添加父节点指针便于回溯调整平衡
平衡因子
_bf
记录子树高度差,用于判断是否需要旋转
cpp
template<class K,class V>
struct AVLTreeNode
{
pair<K, V> _kv; // 存储键值对
AVLTreeNode<K, V>* _left; // 左子节点指针
AVLTreeNode<K, V>* _right; // 右子节点指针
AVLTreeNode<K, V>* _parent; // 父节点指针(方便回溯调整)
int _bf; // 平衡因子:右子树高度 - 左子树高度
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
,_parent(nullptr)
,_bf(0)
{}
};
3、AVL树插入操作详解
插入操作思路:
按照BST规则找到插入位置
插入新节点后,从插入点向上回溯更新平衡因子
根据平衡因子的变化决定是否需要旋转调整
旋转后局部子树恢复平衡,整棵树达到平衡状态
cpp
bool insert(const pair<K, V>& kv)
{
// 1. 空树情况直接插入
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
// 2. 寻找插入位置(标准BST插入逻辑)
Node* cur = _root;
Node* parent = nullptr;
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; // 键已存在,插入失败
}
}
// 3. 创建新节点并插入到正确位置
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
// 4. 更新平衡因子并调整平衡(AVL核心)
while (parent)
{
// 更新父节点的平衡因子
if (cur == parent->_left) // 插入在左子树,平衡因子减1
parent->_bf--;
else if (cur == parent->_right) // 插入在右子树,平衡因子加1
parent->_bf++;
// 判断平衡状态
if (parent->_bf == 1 || parent->_bf == -1)
{
// 树高度变化,但仍在平衡范围内,继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 0)
{
// 树高度不变,更新结束
break;
}
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) // 右子树更高但右子树的左子树更高
RotateRL(parent); // 右左双旋
else if (parent->_bf == -2 && cur->_bf == 1) // 左子树更高但左子树的右子树更高
RotateLR(parent); // 左右双旋
break; // 旋转后子树高度恢复,停止更新
}
else
{
assert(false); // 平衡因子异常,不应该出现
}
}
return true;
}
4、四种旋转操作详解
1. 左单旋(LL型不平衡)
适用场景:当节点的右子树过高(平衡因子为2),且右子节点的右子树过高(右子节点平衡因子为1)

旋转思路:
将右子节点cur提升为新的根
将cur的左子树挂接到原parent的右侧
将原parent变为cur的左子节点
更新所有相关节点的父指针
重置平衡因子
cpp
void RotateL(Node* parent)
{
Node* cur = parent->_right; // 右子节点将成为新的根
// 1. 处理cur的左子树(挂接到parent的右侧)
parent->_right = cur->_left;
if (cur->_left)
cur->_left->_parent = parent;
// 2. 提升cur为新的根
cur->_left = parent;
// 3. 更新父指针关系
Node* pparent = parent->_parent;
parent->_parent = cur;
// 处理原parent的父节点指向
if (pparent == nullptr) // parent是根节点
{
_root = cur;
cur->_parent = nullptr;
}
else // parent不是根节点
{
if (pparent->_left == parent)
pparent->_left = cur;
else
pparent->_right = cur;
cur->_parent = pparent;
}
// 4. 更新平衡因子(旋转后两边高度平衡)
parent->_bf = 0;
cur->_bf = 0;
}
2. 右单旋(RR型不平衡)
适用场景:当节点的左子树过高(平衡因子为-2),且左子节点的左子树过高(左子节点平衡因子为-1)

旋转思路:
将左子节点cur提升为新的根
将cur的右子树挂接到原parent的左侧
将原parent变为cur的右子节点
更新所有相关节点的父指针
重置平衡因子
cpp
void RotateR(Node* parent)
{
Node* cur = parent->_left; // 左子节点将成为新的根
// 1. 处理cur的右子树(挂接到parent的左侧)
parent->_left = cur->_right;
if (cur->_right)
cur->_right->_parent = parent;
// 2. 提升cur为新的根
cur->_right = parent;
// 3. 更新父指针关系
Node* pparent = parent->_parent;
parent->_parent = cur;
// 处理原parent的父节点指向
if (pparent == nullptr) // parent是根节点
{
_root = cur;
cur->_parent = nullptr;
}
else // parent不是根节点
{
if (pparent->_left == parent)
pparent->_left = cur;
else
pparent->_right = cur;
cur->_parent = pparent;
}
// 4. 更新平衡因子
parent->_bf = 0;
cur->_bf = 0;
}
3. 右左双旋(RL型不平衡)
适用场景:当节点的右子树过高(平衡因子为2),但右子节点的左子树过高(右子节点平衡因子为-1)

旋转思路:
先对右子节点进行右旋,转换为LL情况
再对父节点进行左旋
根据旋转前curleft的平衡因子调整各节点的新平衡因子
如果curleft是新增节点(bf=0),所有相关节点平衡因子归零
如果新增节点在curleft左侧(bf=-1),调整cur和parent的平衡因子
如果新增节点在curleft右侧(bf=1),调整parent的平衡因子
cpp
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf; // 保存旋转前的平衡因子
// 1. 先对cur右旋(转换为LL情况)
RotateR(parent->_right);
// 2. 再对parent左旋
RotateL(parent);
// 3. 根据原curleft的平衡因子调整
if (bf == 0) // curleft是新增节点
{
cur->_bf = 0;
curleft->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1) // 新增节点在curleft的左子树
{
cur->_bf = 1;
curleft->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1) // 新增节点在curleft的右子树
{
cur->_bf = 0;
curleft->_bf = 0;
parent->_bf = -1;
}
else
{
assert(false); // 不应该出现其他情况
}
}
4. 左右双旋(LR型不平衡)
适用场景:当节点的左子树过高(平衡因子为-2),但左子节点的右子树过高(左子节点平衡因子为1)

旋转思路:
先对左子节点进行左旋,转换为RR情况
再对父节点进行右旋
根据旋转前curright的平衡因子调整各节点的新平衡因子
如果curright是新增节点(bf=0),所有相关节点平衡因子归零
如果新增节点在curright左侧(bf=-1),调整parent的平衡因子
如果新增节点在curright右侧(bf=1),调整cur的平衡因子
cpp
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
int bf = curright->_bf; // 保存旋转前的平衡因子
// 1. 先对cur左旋(转换为RR情况)
RotateL(parent->_left);
// 2. 再对parent右旋
RotateR(parent);
// 3. 根据原curright的平衡因子调整
if (bf == 0) // curright是新增节点
{
cur->_bf = 0;
curright->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1) // 新增节点在curright的左子树
{
cur->_bf = 0;
curright->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1) // 新增节点在curright的右子树
{
cur->_bf = -1;
curright->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false); // 不应该出现其他情况
}
}
5、平衡检查函数解析
设计思路:
Height
函数递归计算树的高度
IsBalance
函数递归检查:
每个节点的平衡因子是否正确(与实际高度差一致)
每个节点的左右子树高度差不超过1
递归检查所有子树是否平衡
cpp
// 计算树的高度(递归实现)
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 IsBalance()
{
return IsBalance(_root);
}
// 递归检查子树是否平衡
bool IsBalance(Node* root)
{
if (root == nullptr)
return true;
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
// 检查平衡因子是否正确(重要验证)
if (rightHeight - leftHeight != root->_bf)
{
cout << "平衡因子异常:" << root->_kv.first << "->" <<root->_bf << endl;
return false;
}
// 检查高度差和子树平衡
return abs(rightHeight - leftHeight) < 2
&& IsBalance(root->_left)
&& IsBalance(root->_right);
}
6、总结
AVL树通过四种旋转操作维持平衡:
左单旋:处理LL型不平衡
右单旋:处理RR型不平衡
右左双旋:处理RL型不平衡
左右双旋:处理LR型不平衡
每种旋转操作都有其特定的适用场景,通过合理组合这些操作,AVL树能够在插入和删除节点后快速恢复平衡,保证高效的查找性能。平衡检查函数则用于验证树的正确性,是调试和测试的重要工具。