
🦌云深麋鹿
专栏 :C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了 map&set的使用,接下来这篇文章让我们进入到 AVLTree 的学习,体会新的设计思路吧~
放个目录
-
-
- [一 AVL树的概念](#一 AVL树的概念)
- [二 实现](#二 实现)
-
- [2.1 AVLTreeNode](#2.1 AVLTreeNode)
- [2.2 AVLTree](#2.2 AVLTree)
- [2.3 Insert](#2.3 Insert)
- [2.4 AVL树的平衡检测](#2.4 AVL树的平衡检测)
- [2.5 AVL树的查找](#2.5 AVL树的查找)
-
一 AVL树的概念
- AVL树是一棵高度平衡二叉查找树,左右子树高度差的绝对值不超过1。
- 每个结点有一个平衡因子(balance factor),这不是必须的。
- 有些情况下,高度差不为0。
二 实现
2.1 AVLTreeNode
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; // balance factor
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
- 这里的Node存储的是一个pair(键值对),应用场景更广一些。
- 需要额外记录每个Node的_parent,后面要用到。
- 实现的是带平衡因子(后面都简称bf)的AVL树。
2.2 AVLTree
cpp
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
搭个框架。
2.3 Insert
2.3.1 插入流程
(1)新增结点
- 按二叉搜索树规则来。
- 具体在这篇博客 C++ | 二叉搜索树 第三大块。
代码:
cpp
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->_left = cur;
}
else {
parent->_right = cur;
}
cur->_parent = parent;
// ...
}
(2)更新平衡因子
- 新增结点,更新 新增节点 - 根节点 路径上的bf。
- 具体后面展开讲。
(3)旋转
- 更新bf的过程中出现不平衡(左右子树高度差的绝对值超过1),需要旋转不平衡子树。
- 平衡后就不用继续往上更新bf了,完毕。
2.3.2 平衡因子更新
(1)更新原则
- 平衡因子 = 右子树高度 - 左子树高度
- 子树高度变化影响当前结点平衡因子。
- 插入结点parent更新(新结点在左--,在右++)
- 若parent的bf不为零则往上更新。
(2)停止更新条件
- 更新后parent的bf为 零,表示parent所在子树高度不变。则parent的parent结点的bf不会受到影响,更新结束。
- 更新后parent的bf为 1 或 -1,表示parent所在子树原先平衡,更新后高度改变。则parent的parent结点的bf也会受到影响,往上继续更新。
- 更新后parent的bf为 2 或 -2,表示parent所在子树已经不平衡了,需要旋转处理。
- 不断更新到root,bf为 1 或 -1 停止。
(3)代码实现
- 先套一个框架:新增节点 的 parent 存在时,进入循环体。
cpp
while (parent) {
//...
}
- 更新 parent 的 bf:
cpp
if (parent->_kv.first > kv.first) {
parent->_bf--;
}
else {
parent->_bf++;
}
- 判断 parent 的 bf 是否为零:
cpp
if (parent->_bf == 0) {
break;
}else if (abs(parent->_bf) == 1) {
// continue
cur = parent;
parent = parent->_parent;
}else if (abs(parent->_bf) == 2) {
// rotate
break;
}else {
// error
exit(-1);
}
接下来进入rotate(上面第二个else if)。
2.3.3 旋转
旋转共有四种:左旋转、右旋转、左右双旋 和 右左双旋。
(1)旋转原则
- 旋转后保持搜索树的特性。
- 让旋转的树从不平衡变平衡,其次降低旋转树的高度。
(2)右单旋
左子树太高了。
即如图中a子树新增一个结点 高h+1,太高了。

①过程
subL的右子树(subLR)变为parent结点的左子树,subL结点到parent结点的位置上。
- parent上面还有parentParent,后面会用到。
最终实现结果:

②代码实现
框架:
cpp
void RotateR(Node* parent){
//...
}
- 注意修改结点位置也要修改它的_parent。
- 记录各个结点(根据传参parent)
cpp
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;
- 处理parent和subLR
cpp
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
- 存在subLR为nullptr的情况。
- 处理subL和parent
cpp
subL->_right = parent;
parent->_parent = subL;
- 处理parentParent和subL
cpp
if (parentParent) {
if (parentParent->_left == parent) {
parentParent->_left = subL;
}
else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}else {
_root = subL;
_root->_parent = nullptr;
}
- 涉及parentParent存在不存在的问题。
- 若存在,涉及parentParent存在于哪个分支的问题。

- 记得更新bf
cpp
parent->_bf = subL->_bf = 0;
(3) 左单旋
右子树太高了。
即如图中a子树新增一个结点后 高h+1:

①过程
subR的左子树(subRL)变为parent结点的右子树,subR结点到parent结点的位置上。
最终实现结果:

②代码实现
跟右单旋代码类似:
cpp
void RotateL(Node* parent) {
Node* subR = parent->_right;
Node* subRL= subR->_left;
Node* parentParent = parent->_parent;
parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
subR->_left = parent;
parent->_parent = subR;
if (parentParent) {
if (parentParent->_left == parent) {
parentParent->_left = subR;
}
else {
parentParent->_right = subR;
}
subR->_parent = parentParent;
}else {
_root = subR;
_root->_parent = nullptr;
}
parent->_bf = subR->_bf = 0;
}
(4)左右双旋
左子树高了,左子树的右子树高了。
- 即图中 e子树 新增结点后 高h。
- subL 总高h+2,跟c子树高度差超过1了。

①过程
subL左单旋 >> parent右单旋。
实现结果:

②代码实现
框架:
cpp
void RotateRL(Node* parent) {
//...
}
- 记录各个结点
cpp
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;
额外记录一下bf:
cpp
int bf = subLR->_bf;
- 旋转
cpp
RotateL(parent->_left);
RotateR(parent);
这个直接复用写过的代码。
- 重点: 更新bf
cpp
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{
exit(-1);
}
演示一下这几种情况:

(5)右左双旋
右子树高了,右子树的左子树高了。
- 即图中 e子树 新增结点后 高h。

①过程
subR右单旋 >> parent左单旋。
结果:

②代码实现
跟左右双旋类似,直接上代码:
cpp
void RotateRL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* parentParent = parent->_parent;
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 {
exit(-1);
}
}
2.4 AVL树的平衡检测
框架:
cpp
bool IsBalanceTree(Node* root) {
// ...
}
- 处理root为空的情况
cpp
if (root == nullptr) {
return true;
}
- 记录左右子树高度
cpp
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
- 计算高度差,即为 root 的实际bf
cpp
int difference = rightHeight - leftHeight;
- 检查 difference 是否合理
cpp
if(abs(difference) == 2){
cout << root->_kv.first << "abnormal height difference" << endl;
return false;
}
- 检查已存储bf是否正确
cpp
if(difference != root->_bf){
cout << root->_kv.first << "abnormal balance factor" << endl;
return false;
}
- 不能直接检查bf,因为bf可能更新错。
- 递归往root左右判断
cpp
return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
2.5 AVL树的查找
没啥好说的,跟二叉搜索树一样:
cpp
Node* Find(const K& key)
{
Node* cur = _root;
while (cur) {
if (cur->_kv.first < key){
cur = cur->_right;
}else if (cur->_kv.first > key){
cur = cur->_left;
}else{
return cur;
}
}
return nullptr;
}
AVLTree 的学习就到这里,下一篇我们再上一个新的树 红黑树 ,很快会更出来~

