
◆ 博主名称: 晓此方-CSDN博客 大家好,欢迎来到晓此方的博客。
⭐️C++系列个人专栏: 主题曲:C++程序设计
⭐️ 踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰
概要&序論
这里是此方,好久不见。 本文旨在深入剖析 AVL树 ------ 一种自平衡的二叉搜索树。我们将从最核心的概念入手,包括平衡因子、节点高度、旋转操作 ,逐步展开对其算法逻辑、插入调整机制以及实际应用场景的全面解析。让我们现在开始吧!
本文代码示例及测试所需要的头文件:
cpp
#pragma once
#include<iostream>
#include<cassert>
#include<vector>
#include<ctime>
using namespace std;
一,AVL树的概念:最早的自平衡二叉树
1.1什么是AVL树
AVL树是最先发明的自平衡二叉搜索树,AVL是一颗空树,或者具备下列性质的二叉搜索树:它的左右子树都是AVL树,且左右子树的高度差的绝对值不超过1。AVL树是一颗高度平衡搜索二叉树 ,通过控制高度差去控制平衡。
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis是两个前苏联的科学家,他们在1962年的论文《An algorithm for the organization of information》 中发表了它。
1.2平衡因子的概念
AVL树实现这里我们引入一个平衡因子(balance factor)的概念,每个结点都有一个平衡因子,任何结点的平衡因子等于右子树的高度减去左子树的高度,也就是说任何结点的平衡因子等于0/1/-1,AVL树并不是必须要平衡因子 ,但是有了平衡因子可以更方便我们去进行观察和控制树是否平衡,就像一个风向标一样。
Tips: 为什么高度差不超过1
思考一下为什么AVL树是高度平衡搜索二叉树,要求高度差不超过1,而不是高度差是0呢?0不是更好的平衡吗?画画图分析我们发现,不是不想这样设计,而是有些情况是做不到高度差是0的。 比如一棵树是2个结点,4个结点等情况下,高度差最好就是1,无法做到高度差是0。

1.3AVL树的优势
AVL树整体结点数量和分布和完全二叉树类似,高度可以控制在 logN ,那么增删查的效率也可以控制在 O(logN),相比二叉搜索树有了本质的提升。

二,搭建总体结构:AVL树和二叉树的结构有何不同
还是和二叉搜索树一样,我们先写一个总的结构。
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)
{}
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//...
我们看看相比二叉搜索树,AVL树的结点结构多了什么?首先是平衡因子,我们上面讲过了。然后是parent指针,没错,AVL树最适合的结构不是二叉链而是三叉链。为什么这么设计我们先卖一个关子。
三,实现AVL树的插入:在二叉树的基础上维护平衡因子
3.1首先按照二叉搜索树的规则进行插入
AVL树,他首先是一颗二叉搜索树 ,那么插入一个值就应该按二叉搜索树规则进行插入。我这里直接CV一下上一篇文章的代码,没有搞明白的小伙伴可以去看我的上一篇博文哦。注意一下插入后要多一步链接父亲结点。
cpp
bool Insert(const K& key,const V& value){
if (_root == nullptr){
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur){
if (cur->_key > key){
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key){
parent = cur;
cur = cur->_right;
}
else if (cur->_key == key)
return false;
}
cur=new Node(key, value);
if (parent->_key > cur->_key)
parent->_left = cur;
else if (parent->_key < cur->_key)
parent->_right = cur;
cur->_parent = parent;
//..........
return true;
}
3.2平衡因子的更新
新增结点以后,只会影响祖先结点的高度,也就是可能会影响部分祖先结点的平衡因子,所以更新从新增结点->根结点路径上的平衡因子,实际中最坏情况下要更新到根,有些情况更新到中间就可以停止了,具体情况我们下面再详细分析。
3.2.1更新平衡因子的原则
- 平衡因子 = 右子树高度 - 左子树高度(可以是左-右,但是一般都是右-左)
- 只有当前结点子树高度变化才会影响当前结点平衡因子。
- 插入结点,会增加高度所以:
新增结点在 parent 的右子树,parent 的平衡因子 ++,新增结点在 parent 的左子树,parent 平衡因子 --- parent 所在子树的高度是否变化决定了是否会继续往上更新。
收回伏笔 :向上查找更新平衡因子。所以需要三叉链结构,这样方便一点 。有的牛逼的人会选择不去存这个 parent,而是用一个容器(栈或队列)来存储这个插入结点的所有祖先,然后按照一个指定的顺序来查找修改平衡因子。
3.2.2更新停止条件
根据平衡因子的更新结果,有三种情况:
3.2.2.1情况一:更新后 parent 的平衡因子等于 0
这种情况怎么来的? 必然是更新中 parent 的平衡因子从 -1->0 或者 +1->0。
更新前平衡因子是+1/-1,说明更新前 parent 子树一边高一边低 ,新增的结点插入在低的那边,插入后 parent 所在的子树高度不变 ,不会影响 parent 的父亲结点的平衡因子,更新结束。
防止没有听明白,我找个图给大家看看:

如图,parent结点(3)的平衡因子从1变成0,插入后 parent 所在的子树高度不变,不会影响 parent 的父亲结点的平衡因子,更新结束。
3.2.2.2情况二:更新后 parent 的平衡因子等于 1 或 -1
更新前更新中 parent 的平衡因子变化为 0->1 或者 0->-1。
为什么不可能是从+2/-2更新到+1/-1? 因为插入新的结点才会引发更新,而插入新的结点的大前提 是
在插入之前你插入的是一颗AVL树。而存在某一个结点的平衡因子是+2/-2,这就tm不是一颗AVL树。
说明更新前 parent 子树两边一样高,新增的插入结点后,parent 所在的子树一边高一边低 ,parent 所在的子树符合平衡要求,但是高度增加了 1,会影响 parent 的父亲结点的平衡因子,所以要继续向上更新。
还是找一张图来解释:

如图,插入结点后,parent(6)从0->1,所在子树高度发生变化,继续向上更新;parent(3)从0->1,所在子树高度发生变化,继续向上更新,以此类推直到更新到根节点,再不可向上继续更新,更新方才结束。
3.2.2.3情况三:更新后 parent 的平衡因子等于 2 或 -2
更新前更新中 parent 的平衡因子变化为 1->2 或者 -1->-2,说明更新前 parent 子树一边高一边低。新增的插入结点在高的那边,parent 所在的子树高的那边更高了,破坏了平衡。 平衡因子异常

如上图,parent(14)平衡因子从0->-1,所在子树高度发生变化,继续向上调整 ,parent(10)平衡因子从1->2,平衡因子出现异常。
parent 所在的子树不符合平衡要求,需要旋转处理 ,旋转的目标有两个:1、 把 parent 子树旋转平衡。2、 降低 parent 子树的高度,恢复到插入结点以前的高度。所以旋转后也不需要继续往上更新,插入结束。
3.2.3更新平衡因子代码
我们直接接着上面的代码继续写了(文章结尾有完整代码)
cpp
while (parent){
if (parent->_left == cur)
parent->_bf--;
else if (parent->_right == cur)
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){
//旋转
break;
}
else assert(false);
}
首先第一步: 更新平衡因子:如果插入的结点在父亲结点的左边,就--,在右边就++。
然后第二步 :根据更新后的平衡因子做出判断(符合我们上面说的思路 )
补充一点:"防御性编程技巧" :在最后加上else assert。为什么?因为这种情况在预料种不可能发生,如果发生了一定是前面哪一部分的代码出现了问题,这个时候根据assert报错位置向上debug即可。(省的在海量的代码中摸索半天)
万事大吉,来到本文最麻烦的部分------旋转逻辑
四,旋转逻辑与实现:AVL树的四种旋转操作详解
在上面我们了解到,在更新完平衡因子后由+1/-1->+2/-2时,AVL树不再平衡,这个时候就需要对不平衡结点进行旋转。
4.1旋转的原则
旋转有两大重要原则,只有满足这两大原则,旋转才是有意义的:
- 保持搜索树的规则
- 让旋转的树从不平衡变平衡 ,其次降低旋转树的高度
旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。 ,接下来我们一一讲解。(说明:下面的图中,有些结点我们给的是具体值,如10和5等结点,这里是为了方便讲解,实际中是什么值都可以,只要大小关系符合搜索树的性质即可。)
4.2右单旋原理:从抽象到具体详解
4.2.1发生右单旋的抽象场景
如下图,展示的是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为根的树左边太高了,需要往右边旋转。

4.2.2如何进行右单旋
旋转核心步骤:
- 因为5 < b子树根的值 < 10,将b变成10的左子树。
- 5要转上去,所以10变成5的右子树。
- 5变成这棵树新的根。
完成这三步之后,①符合搜索树的规则,②控制了平衡,③以10为根的这棵的高度恢复到了插入之前的h+2(左右高度差抹平,如图 ),符合旋转原则。如果插入之前10是整棵树的一个局部子树,旋转后不会再影响上一层,插入结束了。

4.2.3发生右单旋的具体场景
看完上面的抽象旋转场景,肯定还有很多人云里雾里,接下来我们拆开每一颗抽象的子树内部的具体情况来分析。
4.2.3.1场景一:a/b/c三颗子树的高度都为0
这是最简单的一种情况,a、b、c 都只是空节点。当在 a 子树中插入新节点后,a所在是子树的高度从 0 变为 1,不断向上调整平衡因子,导致 10 的平衡因子从 -1 变成 -2,树失去平衡。
此时对 10 进行一次右旋,5 上升为新的根节点,b 挂到 10 的左子树位置,树重新恢复平衡。

4.2.3.2场景二:a/b/c三颗子树的高度都为1
当三棵子树高度为 1 时,内部结构会稍微复杂一些,但整体逻辑完全一致。如果在 a 子树中继续插入节点,a 的高度增加,最终使 10 的平衡因子变为 -2。
此时同样通过一次右旋,将 5 提升为根节点,恢复 AVL 树的平衡。

4.2.3.3场景三:a/b/c三颗子树的高度都为2
从现在开始,场景急剧复杂化。思考一个问题:当a/b/c子树高度为2时,子树的形状有多少种可能?给出答案如下图:一共有三种,我们标记为x,y,z。

b和c子树可以是xyz 三种情况中的任意一种 ,但是a就不一样了,先说结论:a子树的情况只有可能是x。怎么证明? 先看我们要做的事是在a子树插入一个结点。
细分情况一: 如果a子树是y情况,那么在左子树插入后,a子树就不平衡了,如下图,当平衡因子更新到a子树的根节点就引发了不平衡,于是直接以a子树的根为旋转结点进行右旋转,旋转后不再继续向上更新 。即:在到达10之前提前结束。

细分情况二: 如果a子树是z情况,那么在左子树插入后,a子树高度不变,如下图,当平衡因子更新到a子树的根节点,a子树根节点平衡因子从1->0,于是以a为根的子树高度不变,不再继续向上更新 。即:同样在到达10之前提前结束。

所以,a子树必须是x情况。在a子树插入结点的情况有4种,b和c子树各有3种情况,总计4x3x3=36种。

4.2.3.4场景四~无穷:a/b/c三颗子树的高度都为3乃至更高
当子树高度继续增大时,场景开始逐渐超出枚举范畴。还是思考一个问题:当a/b/c子树高度为3时,子树的形状有多少种可能?

如图,x:为高度为3的满二叉树的AVL树。y-C:代表一个组合,下面四个叶子节点保留任意1个/任意两个/任意3个 ,都满足高度3的AVL树。合计:C(4,1)+C(4,2)+C(4,3) = 4+6+4 = 14种形状。
b和c可以是x/y-C中任意一种,组合:15*15,a的情况跟场景三类似,要满a必须插入新结点后,a自身不旋转,a高度+1不段向上更新,引发10结点旋转。
- a如果是x,插入位置可以是4个叶子的任意孩子位置,有8个。
- a如果是y-C中4个叶子节点保留3个有4种形状,插入位置在有两个结点那边任意孩子位置,有4个。
组合一下:这里合计1515(8+4) = 5400种场景 ,由此可见,当子树的高度达到3的时候,情况种数就达到了如此高的水平(这就是前文为什么采用抽象图来解释原理的原因)。
科普一下
子树高度达到 4 时情况种数:31255875
子树高度达到 5 时的情况种数:1283479530046875
子树高度达到 6 时,理论结构组合已经达到 10³⁰ 级别,远远超过任何可以枚举的规模,因此 AVL 分析必须使用抽象子树模型。
4.2.3.5总结结论
可以看到,无论 a / b / c 子树的高度是多少,只要满足抽象模型中的结构关系 ,发生失衡时都可以通过一次右旋恢复平衡。因此,前面的抽象模型实际上已经涵盖了所有可能的具体情况。
4.3右单旋代码:复杂细节精密处理
直接看代码,我们在注释中详细介绍每一步的意义。 ,为了方便大家理解,我把抽象图再搬过来一份:

cpp
void RotateR(Node* parent) {
//第一步:标记,结合上图,命名和图中一致
Node* pParent = parent->_parent;
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
//进行旋转
SubL->_right = parent;//10结点转下来挂在5结点的右边
parent->_left = SubLR;//5结点的左子树给10结点当左孩子
//旋转后的细节调整
SubL->_parent = pParent;//5结点变成新的根
if (SubLR)
SubLR->_parent = parent;//父亲指针指向调整
parent->_parent = SubL;//父亲指针指向调整
//如果10结点原来是整棵树的根
if (parent == _root)
{
_root = SubL;//根得到重定义
SubL->_parent = nullptr;
}
//如果10结点原来不是整颗数的根
else if (pParent)
{
//原来10结点的父亲调整孩子指针
if (pParent->_left == parent)
pParent->_left = SubL;
else pParent->_right = SubL;
}
//平衡因子调整
parent->_bf = 0;
SubL->_bf = 0;
}
4.4左单旋代码:逻辑完全一致的复现
左单旋的原理和右单旋完全一致,只是方向完全相反。原理不再解释,以下我们直接给出代码:

cpp
void RotateL(Node* parent) {
//第一步:标记,结合上图,命名和图中一致
Node* pParent = parent->_parent;
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
//进行旋转
SubR->_left = parent; //10结点转下来挂在15结点的左边
parent->_right = SubRL; //15结点的左子树给10结点当右孩子
//旋转后的细节调整
SubR->_parent = pParent; //15结点变成新的根
if (SubRL)
SubRL->_parent = parent; //父亲指针指向调整
parent->_parent = SubR; //父亲指针指向调整
//如果10结点原来是整棵树的根
if (parent == _root)
{
_root = SubR; //根得到重定义
SubR->_parent = nullptr;
}
//如果10结点原来不是整颗树的根
else if (pParent)
{
//原来10结点的父亲调整孩子指针
if (pParent->_left == parent)
pParent->_left = SubR;
else pParent->_right = SubR;
}
//平衡因子调整
parent->_bf = 0;
SubR->_bf = 0;
}
4.5左右双旋原理:当单旋不再有效
4.5.1从具体案例引入
如图,上面我们在a子树插入结点的时候,进行右单旋解决了AVL树失衡的问题。此时我们将目光转移到左子树,如果在左子树插入 ,如下图:
b子树高度从h变成h+1,引发旋转,右单旋无法解决问题,右单旋后,我们的树依旧不平衡。

为什么呢?右单旋解决的纯粹的左边高,但是插入在b子树中,10为跟的子树不再是单纯的左边高,对于10是左边高,但是对于5是右边高,于是需要用两次旋转才能解决 。以5为旋转点进行一个左单旋,以10为旋转点进行一个右单旋,这棵树就平衡了,如下图,我们以上面的后者情况为例:

4.5.2发生左右双旋的抽象场景
下面我们还是将a/b/c子树抽象为高度h的AVL子树进行分析 ,另外我们需要把b子树的细节进一步展开为b子树根和高度为h-1的e和f子树,因为 我们要对b的父亲5为旋转点进行左单旋,左单旋需要动b树中的左子树。
b子树中新增结点的位置不同,平衡因子更新的细节也不同,通过观察b子树根 的平衡因子不同,这里我们要分三个场景讨论。
------------------------b子树根的平衡因子是旋转后平衡因子调整的根本

4.5.2.1情况一
h >= 1时,新增结点插入在e子树,e子树高度从h-1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为-1,旋转后8和5平衡因子为0,10平衡因子为1。

4.5.2.2情况二
h >= 1时,新增结点插入在f子树,f子树高度从h-1变为h并不断更新8->5->10平衡因子,引发旋转,其中8的平衡因子为1 ,旋转后8和10平衡因子为0,5平衡因子为-1。

4.5.2.3情况三
h == 0时,a/b/c都是空树,b自己就是一个新增结点,不断更新5->10平衡因子,引发旋转,其中8的平衡因子为0 ,旋转后8和10和5平衡因子均为0。

我们可以在其中总结出一个结论:左右双旋的本质是将SubLR变成新的根,让SubL和parent分别变成SubLR的右左子树,让SubLR的左右子树分别变成SubL和parent的右子树和左子树。
4.6左右双旋代码
cpp
void RotateLR(Node* parent){
//标记结点名称
Node* cur = parent->_left;
Node* SubL = cur;
Node* SubLR = cur->_right;
//保存SubLR的平衡因子,后面维护平衡因子可以根据结论+bf直接得出
int bf = SubLR->_bf;
//旋转
RotateL(SubL);
RotateR(parent);
//维护平衡因子
if (bf == 1) {
parent->_bf = 0;//防御性编程需要:
SubLR->_bf = 0;//如果旋转函数写错了,没有在函数/数内部调整好平衡因子时候采取的补救措施。
SubL->_bf = -1;//SubLR子树中高为h的那一颗给了parent,h-1的那一颗给了SubL
}
else if (bf == -1) {
SubL->_bf = 0;
SubLR->_bf = 0;
parent->_bf = 1;//SubLR子树中高为h的那一颗给了SubL,h-1的那一颗给了parent
}
else if (bf == 0){
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 0;
}
else
assert(false);//防御性编程需要:不可能发生的情况
}
4.7右左双旋代码
右左双旋的代码和左右双旋的代码逻辑几乎一致,只是方向完全相反 ,以下直接给出代码和情况图解。

cpp
void RotateRL(Node* parent) {
//标记结点名称
Node* cur = parent->_right;
Node* SubR = cur;
Node* SubRL = cur->_left;
//保存SubLR的平衡因子,后面维护平衡因子可以根据结论+bf直接得出
int bf = SubRL->_bf;
//旋转
RotateR(SubR);
RotateL(parent);
//维护平衡因子
if (bf == 1) {
SubRL->_bf = 0;//防御性编程需要:
SubR->_bf = 0;//如果旋转函数写错了,没有在函数/数内部调整好平衡因子时候采取的补救措施。
parent->_bf = -1;//SubLR子树中高为h的那一颗给了SubL,h-1的那一颗给了parent
}
else if (bf == -1) {
SubRL->_bf = 0;
parent->_bf = 0;
SubR->_bf = 1;//SubLR子树中高为h的那一颗给了parent,h-1的那一颗给了SubL
}
else if (bf == 0) {
SubR->_bf = 0;
SubRL->_bf = 0;
parent->_bf = 0;
}
else
assert(false);//防御性编程需要
}
以上,我们实现了所有的旋转代码,现在将这些代码接入插入接口的实现中,代码如下:
cpp
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)//左右
RotateLR(parent);
else if (parent->_bf == 2 && cur->_bf == -1)//右左
RotateRL(parent);
else
assert(false);
break;
}
五,AVL树的判定
如何去检查我这棵树是不是AVL树,很多人第一时间会想到:直接检查平衡因子?看似可行,但是在工业中是严格不允许的。
- 有没有可能我这个平衡因子可能是对的,但是因为一种出乎意料的错误导致AVL树是错的.
- 有没有一种可能我的树是对的,但是在更新平衡因子的时候因为一些意想不到的错误出了问题。
所以我们必须对AVL树的每一个结点挨个儿去检查左右子树高度差才行。
cpp
// 对外接口:用于判断整棵树是否为AVL树
// 从根节点开始递归检查
bool IsBalanceTree()
{
return JudgeAVLTree(_root);
}
// 递归检查AVL树是否合法
bool JudgeAVLTree(Node* root)
{
// 递归终止条件:
// 如果当前节点为空,说明已经走到叶子节点之后
// 空树天然满足AVL树性质
if (root == nullptr)
return true;
// 重新计算当前节点左右子树的真实高度
// 注意:这里不相信节点中维护的bf,而是通过height函数重新计算
int leftheight = height(root->_left);
int rightheight = height(root->_right);
// 根据高度计算当前节点的真实平衡因子
// 平衡因子 = 右子树高度 - 左子树高度
int Bf = rightheight - leftheight;
// 第一层检查:AVL树的基本性质
// 任意节点左右子树高度差不能超过1
if (abs(Bf) >= 2)
{
// 如果高度差>=2,说明树结构已经失衡
cout << root->_key << " height error" << endl;
return false;
}
// 第二层检查:节点中存储的平衡因子是否正确
// 在AVL树实现中,节点通常会维护一个_bf成员
// 如果旋转或更新过程中出现错误,就可能导致_bf与真实值不一致
if (root->_bf != Bf)
{
// 说明平衡因子维护出现问题
cout << root->_key << "balance factor error" << endl;
return false;
}
// 递归检查左右子树
// 只有当前节点、左子树、右子树全部满足AVL条件,整棵树才是AVL树
return JudgeAVLTree(root->_left) && JudgeAVLTree(root->_right);
}
六,剩余简单杂项接口与完整代码参考
注意:AVL树的删除代码这里没有写,原因有二:①面试很少涉及到这种难度,②难度太大了。 有兴趣的大佬可以去看《殷人昆 数据结构:用面向对象方法与C++语言描述》,其中有详细讲解。
cpp
#pragma once
#include<iostream>
#include<cassert>
#include<vector>
#include<ctime>
using namespace std;
template <class K,class V>
struct AVLTreeNode
{
AVLTreeNode* _left;
AVLTreeNode* _right;
AVLTreeNode* _parent;
int _bf;//Balance Factor
K _key;
V _value;
AVLTreeNode(const K& key,const V& value)
:_key(key)
,_value(value)
,_bf(0)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
{}
};
template <class K,class V>
class AVLTree
{
using Node = AVLTreeNode<K, V>;
public:
void RotateL(Node* parent){
Node* pParent = parent->_parent;
Node* cur = parent->_right;
Node* nodeC = cur->_left;
cur->_left = parent;
parent->_parent = cur;
parent->_right = nodeC;
if (nodeC)
nodeC->_parent = parent;
cur->_parent = pParent;
if (parent == _root) {
_root = cur;
cur->_parent = nullptr;
}
if (pParent){
if (pParent->_key < cur->_key)
pParent->_right = cur;
else if (pParent->_key > cur->_key)
pParent->_left = cur;
}
cur->_bf = 0;
parent->_bf = 0;
}
void RotateR(Node* parent) {
Node* pParent = parent->_parent;
Node* cur = parent->_left;
Node* nodeC = cur->_right;
cur->_right = parent;
parent->_left = nodeC;
parent->_parent = cur;
cur->_parent = pParent;
if (nodeC)
nodeC->_parent = parent;
if (parent == _root){
_root = cur;
cur->_parent = nullptr;
}
if (pParent){
if (pParent->_key > cur->_key)
pParent->_left = cur;
else if (pParent->_key < cur->_key)
pParent->_right = cur;
}
cur->_bf = 0;
parent->_bf = 0;
}
void RotateLR(Node* parent) {
Node* cur = parent->_left;
Node* SubL = cur;
Node* SubLR = cur->_right;
int bf = SubLR->_bf;
RotateL(SubL);
RotateR(parent);
if (bf == 1) {
parent->_bf = 0;
SubLR->_bf = 0;
SubL->_bf = -1;
}
else if (bf == -1) {
parent->_bf = 1;
SubL->_bf = 0;
SubLR->_bf = 0;
}
else if (bf == 0) {
SubLR->_bf = 0;
SubL->_bf = 0;
parent->_bf = 0;
}
else
assert(false);
}
void RotateRL(Node* parent) {
Node* cur = parent->_right;
Node* SubR = cur;
Node* SubRL = cur->_left;
int bf = SubRL->_bf;
RotateR(SubR);
RotateL(parent);
if (bf == 1) {
SubRL->_bf = 0;
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1) {
SubRL->_bf = 0;
SubR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0) {
SubR->_bf = 0;
SubRL->_bf = 0;
parent->_bf = 0;
}
else
assert(false);
}
bool Insert(const K& key,const V& value){
if (_root == nullptr){
_root = new Node(key, value);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur){
if (cur->_key > key){
parent = cur;
cur = cur->_left;
}
else if (cur->_key < key){
parent = cur;
cur = cur->_right;
}
else if (cur->_key == key)
return false;
}
cur=new Node(key, value);
if (parent->_key > cur->_key)
parent->_left = cur;
else if (parent->_key < cur->_key)
parent->_right = cur;
cur->_parent = parent;
while (parent){
if (parent->_left == cur)
parent->_bf--;
else if (parent->_right == cur)
parent->_bf++;
else
assert(false);
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)//左单
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;
}
Node* Find(const K& key){
Node* cur = _root;
while (cur){
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
int Size() { return size(_root); }
int Height(){return height(_root);}
void Inorder(){inorder(_root); cout << endl;}
bool IsBalanceTree(){return JudgeAVLTree(_root);}
private:
bool JudgeAVLTree(Node* root) {
if (root == nullptr)
return true;
int leftheight = height(root->_left);
int rightheight = height(root->_right);
int Bf = rightheight - leftheight;
if (abs(Bf) >= 2)
{
cout << root->_key << " height error" << endl;
return false;
}
if (root->_bf != Bf)
{
cout << root->_key << "balance factor error" << endl;
return false;
}
return JudgeAVLTree(root->_left) && JudgeAVLTree(root->_right);
}
int size(Node* root){
if (root == nullptr)
return 0;
int leftsize = size(root->_left);
int rightsize = size(root->_right);
return leftsize + rightsize + 1;
}
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;
}
void inorder(Node* root){
if (root == nullptr)
return;
inorder(root->_left);
cout << root->_key << ":" << root->_value << endl;
inorder(root->_right);
}
private:
Node* _root=nullptr;
};
好了,本期内容到此结束,我是此方,我们下期再见。バイバイ!