【C++】AVL树

【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》 中发表了它

文章目录

一、 什么是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、插入的整体流程

  1. 按二叉搜索树(BST)的规则插入新结点
  2. 从新结点向上回溯,更新沿途所有祖先结点的平衡因子
  3. 根据平衡因子的结果,判断是否平衡
  4. 平衡因子为0/1/-1:继续向上更新(或停止)
  5. 平衡因子为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值,分三种情况判断,只要满足一种就停止

  1. bf = 0 :说明插入前父结点的bf是1或-1 ,插入后左右子树高度持平 ,子树高度不变,不会影响上层结点,停止更新
  2. bf = 1 或 -1 :说明插入前父结点的bf是0,插入后子树高度增加1,虽然parent所在的子树符合平衡要求,但高度增加了1,会影响parent的父亲结点的平衡因子,继续向上更新(cur=parent,parent=parent->_parent)
  3. 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树解决不平衡的核心手段,旋转的原则有两个

  1. 旋转后仍然满足二叉搜索树的规则(左小右大)
  2. 旋转后子树恢复平衡,且子树高度恢复到插入前的状态(避免影响上层结点)

根据不平衡的情况,旋转分为四种:右单旋、左单旋、左右双旋、右左双旋 ,其中单旋是基础,双旋是单旋的组合。下面图文分步拆解每一种旋转,标注节点变动、指针指向、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;
}
相关推荐
Ljwuhe1 小时前
C++——模板进阶
开发语言·c++
hehelm1 小时前
AI 大模型接入 SDK —项目概述
linux·服务器·网络·数据库·c++
中国搜索直付通1 小时前
避开直付通选型暗礁:二级商户的合规生存与背景甄别
java·大数据·开发语言·人工智能·游戏
Hesionberger2 小时前
LeetCode406:重建身高队列精髓解析
开发语言·数据结构·python·算法·leetcode
Qimooidea2 小时前
祁木 CAD Translator 深度评测:从参数解析到工程交付实战
java·开发语言·人工智能·机器翻译
小白学大数据2 小时前
企业内网场景下 Python 自定义 CA 证书信任链的正确配置方法
开发语言·网络·python·信任链
Java面试题总结2 小时前
使用 Python 在 Excel 中添加和自定义文本框
开发语言·python·excel
geovindu3 小时前
CSharp: Recursion Algorithm
开发语言·后端·算法·c#·递归算法
dsyyyyy11013 小时前
用JavaScript实现排序算法
开发语言·javascript·排序算法
sTone873753 小时前
写时复制COW的第一性理解
android·c++·flutter