【C++】AVL树
之前我们学习了二叉搜索树(BST) ,发现极端情况下树可能为单支树 ,高度为N,此时时间复杂度为 O(N) ,效率特别低。
而AVL树作为第一个自平衡二叉搜索树,通过严格的高度平衡约束解决了这个问题,让增删查改的效率始终稳定在O(logN)
AVL树得名于它的发明者G.M.Adelson-Velsky和E.M.Landis是两个前苏联的科学家 ,他们在1962 年的论文 《An algorithm for the organization of information》 中发表了它
文章目录
- 【C++】AVL树
-
- [一、 什么是AVL树?](#一、 什么是AVL树?)
- [二、 AVL树的结构](#二、 AVL树的结构)
- [三、 AVL树的插入和平衡调整](#三、 AVL树的插入和平衡调整)
-
- 1、插入的整体流程
- 2、步骤1:按二叉搜索树规则插入新结点
- 3、步骤2:更新平衡因子
- 4、步骤3:旋转调整:AVL树的"平衡魔法"
-
- [(1)场景1:右单旋(RotateR)------ 左左失衡](#(1)场景1:右单旋(RotateR)—— 左左失衡)
- [(2)场景2:左单旋(RotateL)------ 右右失衡](#(2)场景2:左单旋(RotateL)—— 右右失衡)
- [(3)场景3:左右双旋(RotateLR)------ 左右失衡](#(3)场景3:左右双旋(RotateLR)—— 左右失衡)
- [(4)场景4:右左双旋(RotateRL)------ 右左失衡](#(4)场景4:右左双旋(RotateRL)—— 右左失衡)
- 四、辅助接口实现:验证、遍历、查找
- 五、测试用例:验证AVL树的正确性
- 六、完整可运行代码
一、 什么是AVL树?
1、定义
AVL树可以是一颗空树 ,或者满足左右子树都是AVL树 ,并且左右子树的高度差的绝对值不超过1的二叉搜索树
2、平衡因子(bf)
为了方便判断结点是否平衡,我们给每个结点加一个平衡因子 ,计算公式为:
平衡因子 = 右子树高度 - 左子树高度
根据AVL树的平衡要求,任何结点的平衡因子只能是0、1、-1 ,如果出现2或-2 ,说明这个结点所在的子树已经不平衡 ,需要通过旋转调整
3、为什么高度差是1而不是0?
你可能会疑惑,为什么不要求高度差严格为0?其实不是不想,而是实际场景中做不到。比如树有2个、4个结点时,无论怎么分布,左右子树的高度差最小就是1 ,强行要求0会让树的构建变得毫无意义

4、AVL树的优势
AVL树的结点分布和完全二叉树类似 ,树的高度能严格控制在logN级别,这就让它的增删查改效率始终稳定在O(logN) ,相比二叉搜索树有了质的提升

二、 AVL树的结构
和普通二叉搜索树相比,AVL树的结点需要多两个"成员" :父结点指针(parent) 和平衡因子(bf),原因很简单
- parent指针 :插入后更新平衡因子时,需要从插入结点向上遍历到根,父指针能让我们快速回溯
- bf平衡因子 :记录当前结点的平衡状态,作为旋转的判断依据
1、结点结构体
cpp
//结点结构体
template<class K,class V>
struct AVLTreeNode
{
//存储键值对,和map保持一致
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)
{ }
};
2、AVL树类的基础框架
cpp
//AVL树
template<class K,class V>
class AVLTree
{
//重定义结点类型,简化代码
typedef AVLTreeNode<K, V> Node;
public:
//核心接口:插入
bool Insert(const pair<K, V>& kv);
//中序遍历,检验是否满足二叉搜索树特性
void InOrder();
//检验是否是平衡的AVL树
bool IsBalanceTree();
//获取树的高度
int Height();
//查找结点
Node* Find(const K& key);
private:
//递归版中序遍历
void _InOrder(Node* root);
//递归获取树的高度
int _Height(Node* root);
//递归检验平衡
bool _IsBalanceTree(Node* root);
//四种旋转方式,平衡调整
void RotateR(Node* parent); //右单旋
void RotateL(Node* parent); //左单旋
void RotateLR(Node* parent); //左右双旋
void RotateRL(Node* parent); //右左双旋
//根节点
Node* _root = nullptr;
};
三、 AVL树的插入和平衡调整
AVL树的插入是二叉搜索树插入 + 平衡因子更新 + 旋转调整 的组合操作,这也是AVL树的核心难点,我们一步一步拆解,保证每一步都讲透
1、插入的整体流程
- 按二叉搜索树(BST)的规则插入新结点
- 从新结点向上回溯,更新沿途所有祖先结点的平衡因子
- 根据平衡因子的结果,判断是否平衡
- 平衡因子为0/1/-1:继续向上更新(或停止)
- 平衡因子为2/-2:子树不平衡,执行旋转调整,调整后停止更新(旋转后子树高度恢复,不会影响上层)
2、步骤1:按二叉搜索树规则插入新结点
这一步和普通二叉搜索树插入几乎一致,唯一的区别是:
- 插入后要给新结点的parent指针赋值,方便后续回溯
- 插入的新结点平衡因子默认是0(无左右孩子)
cpp
//核心接口:插入
bool Insert(const pair<K, V>& kv)
{
//空树,直接创建根结点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//不是空树,按BST规则找插入位置
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;
}
//给新结点的父指针赋值,AVL树必须有这一步
//目的是插入后能向上回溯,更新平衡因子
cur->_parent = parent;
// 后续:更新平衡因子 + 旋转调整
// ...
}
普通 BST :只做节点挂载,无回溯逻辑
AVL 树 :挂载 + 父指针绑定,为后续回溯更新 bf、旋转做铺垫
3、步骤2:更新平衡因子
平衡因子的更新是从插入结点的父结点开始,向上遍历到根,核心要记住两个规则:
(1)规则1:如何更新平衡因子?
- 新结点插在父结点的左子树 :父结点的bf减1(左子树变高,右-左的结果变小)
- 新结点插在父结点的右子树 :父结点的bf加1(右子树变高,右-左的结果变大)
(2)规则2:何时停止更新?
更新后根据父结点的bf值,分三种情况判断,只要满足一种就停止:
- bf = 0 :说明插入前父结点的bf是1或-1 ,插入后左右子树高度持平 ,子树高度不变,不会影响上层结点,停止更新
- bf = 1 或 -1 :说明插入前父结点的bf是0,插入后子树高度增加1,虽然parent所在的子树符合平衡要求,但高度增加了1,会影响parent的父亲结点的平衡因子,继续向上更新(cur=parent,parent=parent->_parent)
- bf = 2 或 -2 :说明子树不平衡,需要执行旋转调整 ,调整后子树高度恢复,停止更新



(3)平衡因子更新代码
接上面的Insert函数,继续写
cpp
//从父结点开始更新平衡因子
while (parent)
{
//新结点在左,bf--;在右,bf++
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//情况1:bf=0,停止更新
if (parent->_bf == 0)
{
break;
}
//情况2:bf=1/-1,继续向上更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
//情况3:bf=2/-2,旋转调整后停止
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); //右左双旋
}
else
{
// 理论上不会走到这,除非树本身之前就不平衡
assert(false);
}
break;
}
else
{
//bf出现其他值,说明更新出错
assert(false);
}
}
return true;
}
4、步骤3:旋转调整:AVL树的"平衡魔法"
旋转是AVL树解决不平衡的核心手段,旋转的原则有两个:
- 旋转后仍然满足二叉搜索树的规则(左小右大)
- 旋转后子树恢复平衡,且子树高度恢复到插入前的状态(避免影响上层结点)
根据不平衡的情况,旋转分为四种:右单旋、左单旋、左右双旋、右左双旋 ,其中单旋是基础,双旋是单旋的组合。下面图文分步拆解每一种旋转,标注节点变动、指针指向、bf更新
(1)场景1:右单旋(RotateR)------ 左左失衡
- 触发条件 :父结点bf=-2,左孩子bf=-1(由于新节点插入左孩子的左子树,导致左左倾斜)
- 旋转核心 :把左孩子subL提为新根 ,原parent变为subL的右孩子 ,subL的原右孩子变为parent的左孩子
- 如下图,有一个根为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为根的树左边太高了,需要往右边旋转,控制两棵树的平衡
- 旋转核心步骤,因为5<b且b<10,因此把b变为10的左子树 ,10变为5的右子树 ,5变为这棵树新的根 。符合搜索树的规则,控制了平衡,同时这颗树的高度恢复到了插入前的h+2,符合旋转原则 。如果插入之前10整棵树只是一个局部子树,旋转后,也不会再影响上一层,插入结束了

cpp
void RotateR(Node* parent) //右单旋
{
//定义3个关键节点
Node* subL = parent->_left; // subL = 图里的节点 5(10 的左孩子,要当新根)
Node* subLR = subL->right; //subLR 是5的右孩子,b树
Node* parentParent = parent->_parent; // parentParent 对应10的父节点(若10是整棵树的根则为nullptr)
// 步骤1:将subLR(b子树)挂载到parent(10)的左子树位置
parent->_left = subLR;
if (subLR) //若subLR(b子树)不为空,更新其父指针指向parent(10)
{
subLR->_parent = parent;
}
// 步骤2:将parent(10)挂载到subL(5)的右子树位置
subL->_right = parent;
parent->_parent = subL; //更新parent(10)的父指针指向subL(5)
// 步骤3:将subL(5)挂载到原parent(10)的位置(区分是否为整棵树的根)
if (parent == _root)
{
// 若parent(10)原本是整棵树的根,则更新根节点为subL(5)
_root = subL;
subL->_parent = nullptr;
}
else
{
// 若parent(10)是某个节点的子节点,判断其是左子还是右子,将subL(5)挂载上去
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent; // 更新subL(5)的父指针指向原祖父节点
}
// 步骤4:更新平衡因子(右单旋后,parent和subL的平衡因子均归0,对应图中最终状态的0标记)
parent->_bf = subL->_bf = 0;
}
(2)场景2:左单旋(RotateL)------ 右右失衡
- 触发条件 :父结点bf=2,右孩子bf=1(由于新节点插入右孩子的右子树,导致右右倾斜)
- 旋转核心 :与右单旋对称,把右孩子subR提为新根,原parent变为subR的左孩子,subR的原左孩子变为parent的右孩子
- 如下图,有一个根为10的树,有a/b/c抽象为三颗高度为h的子树(h>=0),a/b/c符合AVL树的要求 。10可能是整棵树的根,也可能是一颗树中局部的子树的根。这里a/b/c是高度为h的子树 ,是一种概括抽象表示,它代表了所有左单旋的场景,实际左单旋形态有很多种
- 在a子树中插入一个新节点,导致a子树的高度从h变成h+1 ,不断向上更新平衡因子,导致10的平衡因子从-变成2 ,10为根的树左右高度差超过1, 违反平衡规则 。10为根的树右边太高了,需要往左边旋转,控制两棵树的平衡
- 旋转核心步骤,因为10<b且b<15,因此把b变为10的右子树 ,10变为15的左子树 ,15变为这棵树新的根 。符合搜索树的规则,控制了平衡,同时这颗树的高度恢复到了插入前的h+2,符合旋转原则 。如果插入之前10整棵树只是一个局部子树,旋转后,也不会再影响上一层,插入结束了

cpp
void RotateL(Node* parent) //左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* parentParent = parent->_parent;
// 步骤1:把subRL挂载到parent的右子树
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
// 步骤2:把parent挂载到subR的左子树
subR->_left = parent;
parent->_parent = subR;
//步骤3:把subR挂载到原父结点的位置
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
//步骤4:更新平衡因子为0
subR->_bf = parent->_bf = 0;
}
(3)场景3:左右双旋(RotateLR)------ 左右失衡
- 触发条件 :父结点bf=-2,左孩子bf=1(由于新节点插入左孩子的右子树,导致左右倾斜)
- 旋转核心 :先对左孩子subL做左单旋,转为左左结构,再对原parent做右单旋,最后按subLR的bf更新平衡因子


cpp
void RotateLR(Node* parent) //左右双旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//记录subLR的bf,用于后续更新
int bf = subLR->_bf;
//步骤1:对左孩子做左单旋
RotateL(subL);
//步骤2:对原父结点做右单旋
RotateR(parent);
//根据插入结点的位置更新bf
if (bf == 0)
{
//插入的就是subLR本身,三者bf都为0
subL->_bf = 0;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
// 插入在subLR的右子树,subL的bf=-1,其余为0
subL->_bf = -1;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)
{
// 插入在subLR的左子树,parent的bf=1,其余为0
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
(4)场景4:右左双旋(RotateRL)------ 右左失衡
- 触发条件 :父结点bf=2,右孩子bf=-1(新节点插入右孩子的左子树,右左倾斜)
- 旋转核心 :先对右孩子subR做右单旋,转为右右结构,再对原parent做左单旋,最后按subRL的bf更新平衡因子

cpp
void RotateRL(Node* parent) //右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//记录subRL的bf
int bf = subRL->_bf;
//步骤1:对右孩子做右单旋
RotateR(subR);
//步骤2:对原父结点做左单旋
RotateL(parent);
//根据插入结点的位置更新bf
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if(bf==-1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
四、辅助接口实现:验证、遍历、查找
1、中序遍历:验证是否满足BST特性
AVL树本质还是二叉搜索树,中序遍历结果一定是升序的,这是最基础的验证方式
cpp
//中序遍历,检验是否满足二叉搜索树特性
//对外接口
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//对内递归版中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
//左根右,升序遍历
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
2、平衡验证:判断是否是合格的AVL树
验证逻辑 :递归检查每个结点的平衡因子是否正确,且左右子树高度差的绝对值不超过1
cpp
//检验是否是平衡的AVL树,对外接口
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
//递归检验平衡,对内
bool _IsBalanceTree(Node* root)
{
//空树是AVL树
if (root == nullptr)
return true;
// 计算当前结点的实际平衡因子(右高-左高)
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int actualBF = rightHeight - leftHeight;
// 验证:实际bf和结点存储的bf是否一致,且高度差不超过1
if (abs(actualBF) >= 2 || actualBF != root->_bf)
{
cout << "结点" << root->_kv.first << "平衡因子异常,实际:" << actualBF << ",存储:" << root->_bf << endl;
return false;
}
// 递归验证左右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
3、获取树的高度:辅助平衡验证
cpp
//获取树的高度,对外接口
int Height()
{
return _Height(_root);
}
//递归获取树的高度,对内
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
//返回较高子树的高度+1(当前节点)
return leftH > rightH ? leftH + 1 : rightH + 1;
}
4、查找结点:按BST规则实现,效率O(logN)
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;
}
五、测试用例:验证AVL树的正确性
1、测试用例1:固定用例(包含双旋场景)
cpp
void test01()
{
AVLTree<int, int> t;
// 包含左左、右右、左右、右左所有旋转场景
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
t.Insert({ e,e });
cout << "插入" << e << "后,是否平衡:" << t.IsBalanceTree() << endl;
}
//中序遍历
t.InOrder();
}

2、测试用例2:随机大数量用例(测性能和稳定性)
插入100万个随机值,验证是否始终平衡,同时测试插入和查找效率
cpp
void test02()
{
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand(time(0));
// 生成100万个随机值,避免重复
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 << "插入100万数据耗时:" << end2 - begin2 << "ms" << endl;
cout << "插入后是否平衡:" << t.IsBalanceTree() << endl;
cout << "树的高度:" << t.Height() << endl;
// 测试查找效率
size_t begin1 = clock();
for (size_t i = 0; i < N; i++)
{
t.Find(rand() + i);
}
size_t end1 = clock();
cout << "查找100万次耗时:" << end1 - begin1 << "ms" << endl;
}

六、完整可运行代码
1、AVLTree.h头代码
cpp
#pragma once
#include<assert.h>
#include<cstdlib>
//结点结构体
template<class K, class V>
struct AVLTreeNode
{
//存储键值对,和map保持一致
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)
{
}
};
//AVL树
template<class K, class V>
class AVLTree
{
//重定义结点类型,简化代码
typedef AVLTreeNode<K, V> Node;
public:
//核心接口:插入
bool Insert(const pair<K, V>& kv)
{
//空树,直接创建根结点
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//不是空树,按BST规则找插入位置
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;
}
//给新结点的父指针赋值,AVL树必须有这一步
//目的是插入后能向上回溯,更新平衡因子
cur->_parent = parent;
//从父结点开始更新平衡因子
while (parent)
{
//新结点在左,bf--;在右,bf++
if (cur == parent->_left)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
//情况1:bf=0,停止更新
if (parent->_bf == 0)
{
break;
}
//情况2:bf=1/-1,继续向上更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
//情况3:bf=2/-2,旋转调整后停止
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); //右左双旋
}
else
{
// 理论上不会走到这,除非树本身之前就不平衡
assert(false);
}
break;
}
else
{
//bf出现其他值,说明更新出错
assert(false);
}
}
return true;
}
//中序遍历,检验是否满足二叉搜索树特性
//对外接口
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//检验是否是平衡的AVL树,对外接口
bool IsBalanceTree()
{
return _IsBalanceTree(_root);
}
//获取树的高度,对外接口
int Height()
{
return _Height(_root);
}
//查找结点
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;
}
private:
//对内递归版中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
//左根右,升序遍历
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
//递归获取树的高度,对内
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
//返回较高子树的高度+1(当前节点)
return leftH > rightH ? leftH + 1 : rightH + 1;
}
//递归检验平衡,对内
bool _IsBalanceTree(Node* root)
{
//空树是AVL树
if (root == nullptr)
return true;
// 计算当前结点的实际平衡因子(右高-左高)
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
int actualBF = rightHeight - leftHeight;
// 验证:实际bf和结点存储的bf是否一致,且高度差不超过1
if (abs(actualBF) >= 2 || actualBF != root->_bf)
{
cout << "结点" << root->_kv.first << "平衡因子异常,实际:" << actualBF << ",存储:" << root->_bf << endl;
return false;
}
// 递归验证左右子树
return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}
//四种旋转方式,平衡调整
void RotateR(Node* parent) //右单旋
{
//定义3个关键节点
Node* subL = parent->_left; // subL = 图里的节点 5(10 的左孩子,要当新根)
Node* subLR = subL->_right; //subLR 是5的右孩子,b树
Node* parentParent = parent->_parent; // parentParent 对应10的父节点(若10是整棵树的根则为nullptr)
// 步骤1:将subLR(b子树)挂载到parent(10)的左子树位置
parent->_left = subLR;
if (subLR) //若subLR(b子树)不为空,更新其父指针指向parent(10)
{
subLR->_parent = parent;
}
// 步骤2:将parent(10)挂载到subL(5)的右子树位置
subL->_right = parent;
parent->_parent = subL; //更新parent(10)的父指针指向subL(5)
// 步骤3:将subL(5)挂载到原parent(10)的位置(区分是否为整棵树的根)
if (parent == _root)
{
// 若parent(10)原本是整棵树的根,则更新根节点为subL(5)
_root = subL;
subL->_parent = nullptr;
}
else
{
// 若parent(10)是某个节点的子节点,判断其是左子还是右子,将subL(5)挂载上去
if (parentParent->_left == parent)
{
parentParent->_left = subL;
}
else
{
parentParent->_right = subL;
}
subL->_parent = parentParent; // 更新subL(5)的父指针指向原祖父节点
}
// 步骤4:更新平衡因子(右单旋后,parent和subL的平衡因子均归0,对应图中最终状态的0标记)
parent->_bf = subL->_bf = 0;
}
void RotateL(Node* parent) //左单旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
Node* parentParent = parent->_parent;
// 步骤1:把subRL挂载到parent的右子树
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent;
}
// 步骤2:把parent挂载到subR的左子树
subR->_left = parent;
parent->_parent = subR;
//步骤3:把subR挂载到原父结点的位置
if (parent == _root)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentParent->_left == parent)
{
parentParent->_left = subR;
}
else
{
parentParent->_right = subR;
}
subR->_parent = parentParent;
}
//步骤4:更新平衡因子为0
subR->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent) //左右双旋
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
//记录subLR的bf,用于后续更新
int bf = subLR->_bf;
//步骤1:对左孩子做左单旋
RotateL(subL);
//步骤2:对原父结点做右单旋
RotateR(parent);
//根据插入结点的位置更新bf
if (bf == 0)
{
//插入的就是subLR本身,三者bf都为0
subL->_bf = 0;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
// 插入在subLR的右子树,subL的bf=-1,其余为0
subL->_bf = -1;
parent->_bf = 0;
subLR->_bf = 0;
}
else if (bf == -1)
{
// 插入在subLR的左子树,parent的bf=1,其余为0
subL->_bf = 0;
parent->_bf = 1;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent) //右左双旋
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
//记录subRL的bf
int bf = subRL->_bf;
//步骤1:对右孩子做右单旋
RotateR(subR);
//步骤2:对原父结点做左单旋
RotateL(parent);
//根据插入结点的位置更新bf
if (bf == 0)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = 0;
}
else if (bf == 1)
{
subR->_bf = 0;
subRL->_bf = 0;
parent->_bf = -1;
}
else if(bf==-1)
{
subR->_bf = 1;
subRL->_bf = 0;
parent->_bf = 0;
}
else
{
assert(false);
}
}
//根节点
Node* _root = nullptr;
};
2、test.cpp源文件
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<map>
#include<vector>
#include<stdlib.h>
#include<time.h>
using namespace std;
#include"AVLTree.h"
void test01()
{
AVLTree<int, int> t;
// 包含左左、右右、左右、右左所有旋转场景
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
t.Insert({ e,e });
cout << "插入" << e << "后,是否平衡:" << t.IsBalanceTree() << endl;
}
//中序遍历
t.InOrder();
}
void test02()
{
const int N = 1000000;
vector<int> v;
v.reserve(N);
srand(time(0));
// 生成100万个随机值,避免重复
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 << "插入100万数据耗时:" << end2 - begin2 << "ms" << endl;
cout << "插入后是否平衡:" << t.IsBalanceTree() << endl;
cout << "树的高度:" << t.Height() << endl;
// 测试查找效率
size_t begin1 = clock();
for (size_t i = 0; i < N; i++)
{
t.Find(rand() + i);
}
size_t end1 = clock();
cout << "查找100万次耗时:" << end1 - begin1 << "ms" << endl;
}
int main()
{
//test01();
test02();
return 0;
}