目录
- 前言
- [一、AVL 的概念](#一、AVL 的概念)
- 二、AVL树的实现
-
- [2.1 AVL树的结构](#2.1 AVL树的结构)
- [2.2 AVL树的插入](#2.2 AVL树的插入)
-
- [2.2.1 AVL 树插入一个值的大概过程](#2.2.1 AVL 树插入一个值的大概过程)
- [2.2.2 平衡因子更新](#2.2.2 平衡因子更新)
- [2.2.3 插入节点及更新平衡因子代码实现](#2.2.3 插入节点及更新平衡因子代码实现)
- [2.3 旋转](#2.3 旋转)
-
- [2.3.1 旋转的原则](#2.3.1 旋转的原则)
- [2.3.2 右旋转](#2.3.2 右旋转)
- [2.3.3 右旋转代码实现](#2.3.3 右旋转代码实现)
- [2.3.4 左旋转](#2.3.4 左旋转)
- [2.3.5 左旋转代码实现](#2.3.5 左旋转代码实现)
- [2.3.6 左右双旋](#2.3.6 左右双旋)
- [2.3.7 左右双旋代码实现](#2.3.7 左右双旋代码实现)
- [2.3.8 右左双旋](#2.3.8 右左双旋)
- [2.3.9 右左双旋代码实现](#2.3.9 右左双旋代码实现)
- [2.4 AVL树的平衡检测](#2.4 AVL树的平衡检测)
- [2.5 一些复杂项目的调试技巧](#2.5 一些复杂项目的调试技巧)
- [2.6 AVL树性能测试](#2.6 AVL树性能测试)
- [2.7 AVL树的查找](#2.7 AVL树的查找)
- 三、完整源码
- 结语


🎬 云泽Q :个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》
⛺️遇见安然遇见你,不负代码不负卿~
前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、AVL 的概念
- AVL 树是最先发明的自平衡二叉查找树,AVL 是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是 AVL 树,且左右子树的高度差的绝对值不超过 1。AVL 树是一颗高度平衡搜索二叉树,通过控制高度差去控制平衡。
- AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 E. M. Landis(两个前苏联的科学家),他们在 1962 年的论文《An algorithm for the organization of information》中发表了它。
- AVL 树实现中引入 ** 平衡因子 (balance factor)** 的概念:每个结点都有一个平衡因子,等于右子树的高度减去左子树的高度,取值为 0/1/-1。AVL 树并非必须使用平衡因子,但它能更方便地观察和控制树的平衡状态,如同风向标。
- 思考:为什么 AVL 树要求高度差不超过 1,而非高度差为 0?画图分析可知,部分场景下无法做到高度差为 0(例如 2 个结点、4 个结点的树,高度差最优为 1),因此只能放宽到高度差绝对值不超过 1。
- AVL 树的结点数量和分布与完全二叉树类似,高度可控制在logN,增删查改效率为O(logN),相比普通二叉搜索树有本质提升。

二、AVL树的实现
2.1 AVL树的结构
cpp
// key_value结构
template<class K, class V>
struct AVLTreeNode {
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
// balance factor 平衡因子
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{
}
};
template<class K, class V>
struct AVLTree {
typedef AVLTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
2.2 AVL树的插入
2.2.1 AVL 树插入一个值的大概过程
- 插入一个值按二叉搜索树规则进行插入。
- 新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点 -> 根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。
- 更新平衡因子过程中没有出现问题,则插入结束。
- 更新平衡因子过程中出现不平衡,对不平衡子树旋转,旋转后本质调平衡的同时,降低了子树的高度,不会再影响上一层,所以插入结束。
2.2.2 平衡因子更新
更新原则:
- 平衡因子 = 右子树高度 - 左子树高度
- 只有子树高度变化才会影响当前结点平衡因子。
- 插入结点,会增加高度,所以新增结点在 parent 的右子树,parent 的平衡因子 加加,新增结点在 parent 的左子树,parent 平衡因子 减减
- parent 所在子树的高度是否变化决定了是否会继续往上更新
更新停止条件:
- 更新后 parent 的平衡因子等于 0,更新中 parent 的平衡因子变化为 - 1->0 或者 1->0,说明更新前 parent 子树一边高一边低,新增的结点插入在低的那边,插入后 parent 所在的子树高度不变,不会影响 parent 的父亲结点的平衡因子,更新结束。
- 更新后 parent 的平衡因子等于 1 或 -1,更新前更新中 parent 的平衡因子变化为 0->1 或者 0->-1,说明更新前 parent 子树两边一样高,新增的插入结点后,parent 所在的子树一边高一边低,parent 所在的子树符合平衡要求,但是高度增加了 1,会影响 parent 的父亲结点的平衡因子,所以要继续向上更新。
- 更新后 parent 的平衡因子等于 2 或 -2,更新前更新中 parent 的平衡因子变化为 1->2 或者 -1->-2,说明更新前 parent 子树一边高一边低,新增的插入结点在高的那边,parent 所在的子树高的那边更高了,破坏了平衡,parent 所在的子树不符合平衡要求,需要旋转处理,旋转的目标有两个:1、把 parent 子树旋转平衡。2、降低 parent 子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。
- 不断更新,更新到根,根的平衡因子是 1 或 - 1 也停止了。



2.2.3 插入节点及更新平衡因子代码实现
平衡树的逻辑就是按照搜索树的逻辑插入,然后按照下面的逻辑更新平衡因子,若是不平衡再旋转
代码逻辑解析:


往上更新的时候,就把cur挪到parent的位置,然后让parent走到其父节点的位置




2.3 旋转
2.3.1 旋转的原则
- 保持搜索树的规则
- 让旋转的树从不平衡变平衡,其次降低旋转树的高度
旋转总共分为四种,左单旋 / 右单旋 / 左右双旋 / 右左双旋。
说明:下面的图中,有些结点我们给的是具体值,如 10 和 5 等结点,这里是为了方便讲解,实际中是什么值都可以,只要大小关系符合搜索树的性质即可。
2.3.2 右旋转
诱发右旋转的特点是左边高,需要往右边旋转一下才能平衡
- 本图 1 展示的是 10 为根的树,有 a/b/c 抽象为三棵高度为 h 的子树 (h>=0),a/b/c 均符合 AVL 树的要求。10 可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里 a/b/c 是高度为 h 的子树,是一种概括抽象表示,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体图 2 / 图 3 / 图 4 / 图 5 进行了详细描述。
- 在 a 子树中插入一个新结点,导致 a 子树的高度从 h 变成 h+1,不断向上更新平衡因子,导致 10 的平衡因子从 - 1 变成 - 2,10 为根的树左右高度差超过 1,违反平衡规则。10 为根的树左边太高了,需要往右边旋转,控制两棵树的平衡。
- 旋转核心步骤,因为 5 < b 子树的值 < 10,将 b 变成 10 的左子树,10 变成 5 的右子树,5 变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的 h+2,符合旋转原则。如果插入之前 10 整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。







2.3.3 右旋转代码实现
cpp
//右旋转
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;//b变为10的左子树
//不要忘记更新结点的parent
//h = 0时,b(subLR)有可能是空,参考图2
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;//10变为5的右子树
parent->_parent = subL;
//5变为子树的根
//情况1:旋转的10就是根结点
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else {
//情况2:旋转的10就是一颗局部的子树
//有可能是局部的左/右子树
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
//更新5和10的平衡因子,平衡之后均为0
parent->_bf = subL->_bf = 0;
}
2.3.4 左旋转
右旋转是左边高,左旋转就是右边高
- 本图 6 展示的是 10 为根的树,有 a/b/c 抽象为三棵高度为 h 的子树 (h>=0),a/b/c 均符合 AVL 树的要求。10 可能是整棵树的根,也可能是一个整棵树中局部的子树的根。这里 a/b/c 是高度为 h 的子树,是一种概括抽象表示,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体跟上面左旋类似。
- 在 a 子树中插入一个新结点,导致 a 子树的高度从 h 变成 h+1,不断向上更新平衡因子,导致 10 的平衡因子从 1 变成 2,10 为根的树左右高度差超过 1,违反平衡规则。10 为根的树右边太高了,需要往左边旋转,控制两棵树的平衡。
- 旋转核心步骤,因为 10 < b 子树的值 < 15,将 b 变成 10 的右子树,10 变成 15 的左子树,15 变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的高度恢复到了插入之前的 h+2,符合旋转原则。如果插入之前 10 整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。

2.3.5 左旋转代码实现
cpp
//左旋转
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 (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else {
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
parent->_bf = subR->_bf = 0;
}
2.3.6 左右双旋
通过图 7 和图 8 可以看到,左边高时,如果插入位置不是在 a 子树,而是插入在 b 子树,b 子树高度从 h 变成 h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。右单旋解决的纯粹的左边高,但是插入在 b 子树中,10 为根的子树不再是单纯的左边高,对于 10 是左边高,但是对于 5 是右边高,需要用两次旋转才能解决,以 5 为旋转点进行一个左单旋,以 10 为旋转点进行一个右单旋,这棵树就平衡了。




- 图 7 和图 8 分别为左右双旋中
h==0和h==1具体场景分析,下面我们将 a/b/c 子树抽象为高度 h 的 AVL 子树进行分析,另外我们需要把 b 子树的细节进一步展开为 8 和左子树高度为 h-1 的 e 和 f 子树,因为我们要对 b 的父亲 5 为旋转点进行左单旋,左单旋需要动 b 树中的左子树。b 子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察 8 的平衡因子不同,这里我们要分三个场景讨论。 - 场景 1:
h >= 1时,新增结点插入在 e 子树,e 子树高度从 h-1 并为 h 并不断更新 8->5->10 平衡因子,引发旋转,其中 8 的平衡因子为 - 1,旋转后 8 和 5 平衡因子为 0,10 平衡因子为 1。 - 场景 2:
h >= 1时,新增结点插入在 f 子树,f 子树高度从 h-1 变为 h 并不断更新 8->5->10 平衡因子,引发旋转,其中 8 的平衡因子为 1,旋转后 8 和 10 平衡因子为 0,5 平衡因子为 - 1。 - 场景 3:
h == 0时,a/b/c 都是空树,b 自己就是一个新增结点,不断更新 5->10 平衡因子,引发旋转,其中 8 的平衡因子为 0,旋转后 8 和 10 和 5 平衡因子均为 0。
场景1

下面是场景1中间过程的细节图

场景2

场景3

2.3.7 左右双旋代码实现
场景1,2,3本质是平衡因子的调节,旋转的过程可以复用之前的代码,平衡因子的调节关键要看8这个结点的平衡因子,调两个单旋就会动平衡因子,调用5左旋就会把5和8的平衡因子变为0,调用10右旋就会把10和5的平衡因子也变为0,所以要在单旋之前做准备
cpp
//左右双旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//预先保存其插入新结点之后的平衡因子
int bf = subLR->_bf;
//左单旋
RotateL(parent->_left);
RotateR(parent);
//subRL的平衡因子可能是0,其自身就是新增
//虽然前面单旋已经把平衡因子更新为0
//但是这里再写的作用是解耦,单旋是否更新与我无关,这里只考虑3个场景
if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
//subRL的平衡因子可能是1,新节点在其右边插入
else if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
//subRL的平衡因子可能是-1,新节点在其左边插入
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else {
//防御性编程,此处还有其他情况则程序出大问题
assert(false);
}
2.3.8 右左双旋
- 跟左右双旋类似,下面我们将 a/b/c 子树抽象为高度 h 的 AVL 子树进行分析,另外我们需要把 b 子树的细节进一步展开为 12 和左子树高度为 h-1 的 e 和 f 子树,因为我们要对 b 的父亲 15 为旋转点进行右单旋,右单旋需要动 b 树中的右子树。b 子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察 12 的平衡因子不同,这里我们要分三个场景讨论。
- 场景 1:
h >= 1时,新增结点插入在 e 子树,e 子树高度从 h-1 变为 h 并不断更新 12->15->10 平衡因子,引发旋转,其中 12 的平衡因子为 - 1,旋转后 10 和 12 平衡因子为 0,15 平衡因子为 1。 - 场景 2:
h >= 1时,新增结点插入在 f 子树,f 子树高度从 h-1 变为 h 并不断更新 12->15->10 平衡因子,引发旋转,其中 12 的平衡因子为 1,旋转后 15 和 12 平衡因子为 0,10 平衡因子为 - 1。 - 场景 3:
h == 0时,a/b/c 都是空树,b 自己就是一个新增结点,不断更新 15->10 平衡因子,引发旋转,其中 12 的平衡因子为 0,旋转后 10 和 12 和 15 平衡因子均为 0。


2.3.9 右左双旋代码实现
cpp
//右左双旋
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//预先保留其插入新节点之后的平衡因子
int bf = subRL->_bf;
//右单旋
RotateR(parent->_right);
//左单旋
RotateL(parent);
if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else {
assert(false);
}
}
2.4 AVL树的平衡检测

单看这个测试用例貌似是没有问题的,然而其是有缺陷的,该测试用例只能根据中序遍历结果判断其是二叉搜索树,无法判断其是否到底平衡以及平衡因子是否处理得当,所以下面还要写一个判断平衡的函数
这个函数是不能只检查平衡因子的,因为平衡因子也是我们自己写的函数更新的,如果只检查所有的平衡因子的绝对值小于2,就有可能出现监守自盗的问题,因为我们的平衡因子就有可能是更新错误的,所以我们实现的AVL树是否合格,还需要通过递归检查左右子树高度差来反向验证才行,同时检查一下结点的平衡因子更新是否出现了问题
cpp
//平衡检测
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
private:
int _Height(Node* root)
{
if (root == nullptr)
return 0;
//后序遍历的方式求取高度
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
//当前树的高度等于左右子树高的那个再加1
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{
if (root == nullptr)
return true;
//左子树高度
int lh = _Height(root->_left);
//右子树高度
int rh = _Height(root->_right);
//打印出具体错误节点
if (rh - lh != root->_bf || abs(root->_bf) >= 2)
return false;
//递归检测左/右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
2.5 一些复杂项目的调试技巧
上面的这一串代码看起来已经相当复杂了,实际上在企业中遇到的代码肯定会比这还复杂,有兄弟就要说了,现在AI这么牛逼我直接把代码扔给AI让他帮我找不就行了吗,但是如果代码的逻辑复杂到AI都解决不了,自己还不会调试,那就非常痛苦了,难道要盯着几千行的项目一行一行的看吗,所以打铁还需自身硬,这里分析一些类似这种稍复杂项目的调试技巧:

我修改了一下代码使得现在这棵树出现了问题

首先可以判断,6节点出问题并不是因为插入的时候导致的,因为6插入的比较早,这里应该是插入完成后出现的问题,现在的首要目标是知道到底是哪个值插入的时候导致的问题,这时候如果使用单步调试的方式来解决就会非常痛苦,需要一步一步插入看树是否对的上

可以看到是插入最后的14的时候出现的问题,接下来就可以尝试使用单步调试了,但是这样的方式也不好,假设这时候数据再多一点,比如有100个,那在Insert打断点就要查找100次,太费劲了,还有一种方式就是打条件断点

还有一种方式就是用代码手动截断,在循环中放一个if条件,然后定义一个变量卡住(不能直接在空语句断,因为在空语句上打不了断点)

此时在14处停下

即将插入14的时候将整棵树画下来

这里就可以大胆猜测一下是右左双旋出现的问题,不放心的话可以看一下平衡因子的更新是否和图画的一样

原因就是我把右左双旋的逻辑注释掉了,这里这么做只是为了演示复杂场景下找Bug的技巧
2.6 AVL树性能测试


v.reserve(N); 的作用
reserve 是 C++ vector 的容量预分配函数 ,专门用于优化 vector 的内存操作。
核心功能 :为 vector 一次性预分配足够容纳 N 个元素的连续内存空间,但不改变 vector 的大小(size 仍为 0),仅修改容量(capacity)。
代码中 N=100000,需要向 vector 插入 10 万个数据;
vector 默认初始容量很小,如果不调用 reserve,频繁 push_back 会触发自动扩容 :扩容机制 = 重新分配更大内存 → 拷贝所有旧元素 → 释放旧内存;10 万次插入会触发多次扩容,扩容本身会消耗大量时间 ,会严重干扰 AVL 树插入性能的测试结果;v.reserve(100000) 直接预分配好 10 万元素的内存,彻底避免扩容,让 vector 的操作零额外开销 ,保证计时只统计AVL 树的插入耗时。
可以看到以我电脑的硬件配置,在DeBug模式下使用AVL树插入10w个数据只需要24ms,插入100w个数据需要283ms
万一在前面的测试没有问题,在这里性能测试的插入出现了问题,那只能尽量缩小数据范围进行测试了(比如说就缩小到100个数据,甚至更小),不然别说100w个数据了,就是2000个数据画一个树都能把人画晕
2.7 AVL树的查找
这里是比较查找随机值和确定在的值的性能差异,是在Release平台下测的

在查找确定在的值的时候100w个值插入了63w个,因为有些重复的没有插入成功,高度是22,220次方是100w多,这里是不是严格的满二叉树,没有问题,插入共耗时165ms,查找0ms的意思是查找这100w个确定在的数据消耗小于1ms

可以看到查找随机值就消耗了32ms
查找确定在的数值更快的原因是确定在的那些值在查找的时候有些数字不会查找到根,有些数据在树的中间或起点靠后一点点就找到停止寻找了。
查找随机值就会存在查找树中没有的值,就会从头找到尾才可以确定不在,所以更耗时,但是总的来说AVL树这个结构的速度还是很快的,也验证了二叉平衡搜索树在实战的角度是非常有意义的
三、完整源码
AVLTree.h
cpp
#pragma once
#include<iostream>
#include<assert.h>
#include<vector>
#include<ctime>
using namespace std;
// key_value结构
template<class K, class V>
struct AVLTreeNode {
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
// balance factor 平衡因子
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{
}
};
template<class K, class V>
struct AVLTree {
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
//cur不为空,继续向下循环找
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);
//新插入结点的平衡因子是0,因为新插入结点子树为空
//前面默认初始化就是0
//cur->_bf = 0;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else {
parent->_left = cur;
}
//AVLTreeNode自带的parent指针要连接上
cur->_parent = parent;
//AVL树的parent指针可以方便更新平衡因子
//控制平衡
//1.更新平衡因子,最坏更新到根
while (parent)
{
if (cur == parent->_left)
{
//左边高度+1,父亲的平衡因子-1
parent->_bf--;
}
else {
//右边高度+1,父亲的平衡因子+1
parent->_bf++;
}
//父亲的平衡因子更新完成后
//是否需要往上更新分三种情况
//1. -1/1->0
if (parent->_bf == 0)
{
//parent所在的子树高度不变,不会影响上一层,更新结束
break;
}
//2. 0->1/-1
//整体树的高度变了,要继续往上更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
//parent所在的子树高度变了,会影响上一层,继续向上更新
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//parent所在的子树已经不平衡了,需要旋转处理
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);
}
else {
assert(false);
}
break;//旋转后不用更新平衡因子
}
else {
//防御性编程
//防止未知情况,比如原来树就不平衡
//再插入结点后平衡因子变为3/-3
assert(false);
}
}
return true;
}
//右旋转
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;//b变为10的左子树
//不要忘记更新结点的parent
//h = 0时,b(subLR)有可能是空,参考图2
if (subLR)
subLR->_parent = parent;
Node* parentParent = parent->_parent;
subL->_right = parent;//10变为5的右子树
parent->_parent = subL;
//5变为子树的根
//情况1:旋转的10就是根结点
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else {
//情况2:旋转的10就是一颗局部的子树
//有可能是局部的左/右子树
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else {
parentParent->_right = subL;
}
subL->_parent = parentParent;
}
//更新5和10的平衡因子,平衡之后均为0
parent->_bf = subL->_bf = 0;
}
//左旋转
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 (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else {
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
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);
//subRL的平衡因子可能是0,其自身就是新增
//虽然前面单旋已经把平衡因子更新为0
//但是这里再写的作用是解耦,单旋是否更新与我无关,这里只考虑3个场景
if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
//subRL的平衡因子可能是1,新节点在其右边插入
else if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
//subRL的平衡因子可能是-1,新节点在其左边插入
else if (bf == -1)
{
parent->_bf = 1;
subLR->_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);
if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else {
assert(false);
}
}
//依靠中序遍历验证
void InOrder()
{
_InOrder(_root);
cout << endl;
}
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;
}
//平衡检测
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
private:
int _Size(Node* root)
{
if (root == nullptr)
return 0;
//左子树高度 + 右子树高度 + 自己
return _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
//后序遍历的方式求取高度
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
//当前树的高度等于左右子树高的那个再加1
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalanceTree(Node* root)
{
if (root == nullptr)
return true;
//左子树高度
int lh = _Height(root->_left);
//右子树高度
int rh = _Height(root->_right);
//打印出具体错误节点
if (rh - lh != root->_bf || abs(root->_bf) >= 2)
{
cout << root->_kv.first << ":平衡因子异常" << endl;
return false;
}
//递归检测左/右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
//依靠中序遍历验证
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
test.cpp
cpp
#include"AVLTree.h"
void TestAVLTree1()
{
AVLTree<int, int> t;
//常规的测试用例 - 只包含单旋
//int a[] = { 16,3,7,11,9,26,18,14,15 };
//包含双旋
int a[] = { 4,2,6,1,3,5,15,7,16,14 };
for (auto e : a)
{
//if (e == 14)
//{
// int i = 0;
//}
t.Insert({ e, e });
//借助打印,每插入一个值检测一下
cout << e << "->" << t.IsBalanceTree() << endl;
}
//t.InOrder();
cout << t.IsBalanceTree() << endl;
}
void TestAVLTree2()
{
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
}
size_t begin2 = clock();
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << "Insert:" << end2 - begin2 << endl;
cout << t.IsBalanceTree() << endl;
cout << "Height:" << t.Height() << endl;
//看实际插入多少个,使用随机值会插入不少重复的值
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
//确定在的值
//for (auto e : v)
//{
// t.Find(e);
//}
////随机值
for (size_t i = 0; i < N; i++)
{
t.Find(rand() + i);
}
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}
int main()
{
//TestAVLTree1();
TestAVLTree2();
return 0;
}
结语
