【C++进阶】AVL树:平衡因子原理、四大旋转与完整代码实现
·
1. AVL树的概念
- AVL树是最先发明的自平衡二叉搜索树。AVL树是一棵空树或是一棵具有以下性质的二叉搜索树:
它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。
AVL树是一棵高度平衡搜索二叉树,通过控制高度差去控制平衡。 - 平衡因子:每个结点都有一个平衡因子,任何结点的平衡因子都等于其右子树的高度减去左子树的高度
任何结点的平衡因子一定等于-1/0/1 - AVL树整体结点数量和分布与完全二叉树类似,高度可以控制在logN,增删查改的效率也可以控制在O(logN)
2.AVL树的实现
2.1 AVL树的结构
template<class K, class V>
struct AVLTreeNode
{
pair<K, V> _kv;
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
int _bf; // 平衡因子
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0) // 新增结点的平衡因子一定是0
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
// ...
private:
Node* _root = nullptr;
};
2.2 AVL树的插入
2.2.1 AVL树插入一个值的大概过程
- 插入一个值按二叉搜索树的规则进行插入。
- 插入结点后,只会影响其祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根节点路径上
的结点的平衡因子,最坏情况下要更新到根。 - 更新平衡因子过程中没有出现问题则插入结束。
- 更新平衡因子过程中如果出现不平衡(平衡因子==2/-2),需要对不平衡子树进行旋转。旋转调平衡的同时也降低了子树的高
度,不会再影响上一层,则插入结束。
2.2.2 平衡因子更新
更新规则:
- 平衡因子 = 右子树高度 - 左子树高度
- 只有子树高度变化才会影响当前结点平衡因子
- 插入结点会增加高度,所以新增节点在parent的右子树,parent的平衡因子++,新增结点在parent的左子树,平衡因子–
- parent的子树高度是否变化决定了是否继续往上更新
更新停止条件:
- 更新后parent的平衡因子等于0,说明parent子树高度不变,不会影响parent父节点的平衡因子,更新结束
- 更新后parent的平衡因子等于1或-1,parent所在子树符合平衡要求,但高度增加了1,会影响parent父节点的平衡因子,
所以要继续向上更新 - 更新后parent的平衡因子等于2或-2,说明AVL树的平衡被破坏,需要进行旋转。旋转后parent所在子树高度降低,所以旋
转后也不需要继续向上更新,插入结束
2.2.3 插入结点及更新平衡因子代码实现
// 插入
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++;
if (parent->_bf == 0)
{
// 更新结束
break;
}
else if (parent->_bf == -1 || parent->_bf == 1)
{
// 继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == -2 || parent->_bf == 2)
{
// 旋转
if (parent->_bf == -2 && cur->_bf == -1)
RotateR(parent);
else if (parent->_bf == 2 && cur->_bf == 1)
RotateL(parent);
else if (parent->_bf == -2 && cur->_bf == 1)
RotateLR(parent);
else if (parent->_bf == 2 && cur->_bf == -1)
RotateRL(parent);
break;
}
else
{
assert(false);
}
}
return true;
}
2.3 旋转
2.3.1 旋转的原则
- 保持搜索树的规则
- 让旋转的树从不平衡到平衡,其次降低树的高度
旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋
2.3.2 右单旋
- 本图展示的是一棵以 10 为根的树,a/b/c 抽象为三棵高度为 h 的子树(h >= 0),a/b/c 均满足 AVL 树的要求。10 可能是整棵树的根,也可能是整棵树中局部的子树的根(图中具体数值可以是任何值,只要大小关系满足 AVL 树的要求即可)。
- 在 a 子树中插入一个新结点,导致 a 子树的高度从 h 变为 h + 1,不断向上更新平衡因子,导致 10 的平衡因子从 -1 变成 -2,10为根的树左右高度差超过1,违法平衡规则。10 为根的树左边高,需要往右边旋转,控制两棵树的平衡。
- 旋转核心步骤:b 变为 5 的左子树,10 变成 5 的右子树,5 变成这棵树新的根,符合搜索树的规则,控制了平衡;同时这棵树的高度恢复到了插入之前的 h + 2,符合旋转原则。旋转后不会影响上一层,插入结束。

2.3.3 右单旋代码实现
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 连接subLR与parent
parent->_left = subLR;
// subLR不为空时才能更改其parent
if (subLR)
subLR->_parent = parent;
// 记录parent的原_parent
Node* parentParent = parent->_parent;
// 连接subL与parent
subL->_right = parent;
parent->_parent = subL;
// parent有可能是整棵树的根,也可能是局部的子树
// 如果是整棵树的根,要修改其_root
if (parentParent == nullptr)
{
_root = subL;
subL->_parent = nullptr;
}
// 如果是局部的子树要与上一层连接
else
{
// 若parent为其原父节点的左孩子结点
// 则修改原父节点的左孩子为subL
if (parent == parentParent->_left)
{
parentParent->_left = subL;
}
// 若parent为其原父节点的右孩子结点
// 则修改原父节点的右孩子为subL
else
{
parentParent->_right = subL;
}
// 将subL的父节点指针指向parent的原_parent
subL->_parent = parentParent;
}
// 此时parent和subL平衡,更新平衡因子为0
subL->_bf = parent->_bf = 0;
}
2.3.4 左单旋
- 本图展示的是一棵以 10 为根的树,a/b/c 抽象为三棵高度为 h 的子树(h >= 0),a/b/c 均满足 AVL 树的要求。10 可能是整棵树的根,也可能是整棵树中局部的子树的根(图中具体数值可以是任何值,只要大小关系满足 AVL 树的要求即可)。
- 在 a 子树中插入一个新结点,导致 a 子树的高度从 h 变为 h + 1,不断向上更新平衡因子,导致 10 的平衡因子从 1 变成 2,10为根的树左右高度差超过1,违法平衡规则。10 为根的树右边高,需要往左边旋转,控制两棵树的平衡。
- 旋转核心步骤:b 变为 10 的右子树,10 变成 15 的左子树,15 变成这棵树新的根,符合搜索树的规则,控制了平衡;同时这棵树的高度恢复到了插入之前的 h + 2,符合旋转原则。旋转后不会影响上一层,插入结束。

2.3.5 左单旋代码实现
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* parentParent = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parentParent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
subR->_bf = parent->_bf = 0;
}
2.3.6 左右双旋
新插入结点在失衡结点的左孩子的右子树上时
需要进行左右双旋。
根据旋转后平衡因子的变化可以分为三种情况
- h >= 1 时,新增结点在 e 子树,e 子树高度从 h - 1 变为 h,并不断更新 8->5 -> 10 的平衡因子,引发旋转,其中 8 的平衡因子为 -1,旋转后 8 和 5 平衡因子为 0,10 平衡因子为 1
- h >= 1 时,新增结点在 f 子树,f 子树高度从 h - 1 变为 h,并不断更新 8->5 -> 10 的平衡因子,引发旋转,其中 8 的平衡因子为 1,旋转后 8 和 10 平衡因子为 0,5 平衡因子为 -1
- h == 0 时, a/b/c 都是空树, b 本身就是新增结点,不断更新 5->10 平衡因子,引发旋转,其中 8 的平衡因子为 0,旋转后 8 和 5 和 10 的平衡因子均为 0

2.3.7 左右双旋代码实现
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(subL);
RotateR(parent);
if (bf == 0)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
subL->_bf = 0;
subLR->_bf = 0;
parent->_bf = 1;
}
else if (bf == 1)
{
subL->_bf = -1;
subLR->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
2.3.8 右左双旋
新插入结点在失衡结点的右孩子的左子树上时
需要进行右左双旋。
根据旋转后平衡因子的变化可以分为三种情况
- h >= 1 时,新增结点在 e 子树,e 子树高度从 h - 1 变为 h,并不断更新 12->15 -> 10 的平衡因子,引发旋转,其中 12 的平衡因子为 -1,旋转后 10 和 12 平衡因子为 0,15 平衡因子为 1
- h >= 1 时,新增结点在 f 子树,f 子树高度从 h - 1 变为 h,并不断更新 12->15 -> 10 的平衡因子,引发旋转,其中 12 的平衡因子为 1,旋转后 15 和 12 平衡因子为 0,10 平衡因子为 -1
- h == 0 时, a/b/c 都是空树, b 本身就是新增结点,不断更新 15->10 平衡因子,引发旋转,其中 12 的平衡因子为 0,旋转后 10 和 12 和 15 的平衡因子均为 0

2.3.9 右左双旋代码实现
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(subR);
RotateL(parent);
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == -1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else
{
assert(false);
}
}
2.4 AVL树查找
AVL 树查找逻辑与二叉搜索树逻辑相同,搜索效率为O(logN)
Node* Find(const K& key)
{
Node* cur = _root;
while (cur) {
if (key > cur->_kv.first)
{
cur = cur->_right;
}
else if (key < cur->_kv.first)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
2.5 AVL 树平衡检测
通过检查左右子树高度差可以验证AVL树是否合格,同时可以检查平衡因子是否出现问题
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
bool _IsBalanceTree(Node* root)
{
if (root == nullptr)
{
// 空树也是AVL树
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;
}
if (diff != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
}
// 左右子树都是AVL树则该树一定是AVL树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
更多推荐


所有评论(0)