C++模拟实现——AVL树

AVL树

1.介绍

AVL树是对搜索二叉树的改进,通过特定的方法使得每个节点的左右子树高度差绝对值不超过1,使得避免出现歪脖子的情况,最核心的实现在于插入值部分是如何去实现平衡调整的,由于前面详细实现和解析过搜索二叉树,因此本篇文章着重整理AVL树核心的部分,插入的实现,以及旋转是如何操作的

2.基本框架

先搭建一个搜索二叉树的基本框架

节点定义部分

平衡因子的概念:

一个节点的平衡因子指的是左右子树的高度差,根据自己定义,可以是左边高度减右边高度,或者是右边高度减左边高度,在AVL树中,调节平衡因子是实现平衡的关键

ps:本篇对平衡因子_bf的定义都是右边减左边

AVL树的框架

3.核心部分

基本框架

插入部分重点还是在于平衡因子的调整部分,前面就不详细分析了

平衡因子的调整

每次插入要保持每个节点的平衡因子绝对值都小于1,则说明整棵树是符合AVL树的规则的,即每个节点的左右子树高度差的绝对值不超过1。

因此,再每次插入一个新的节点时,要考虑每个节点之间平衡因子的变化,通过画图观察发现,当一个新节点插入后,有可能受影响的是其部分或者全部祖先

通过画图观察总结出更新的规律,新增节点后,当新增节点在其父母节点的左边则对父母节点的bf进行--,在右边则进行++(这是因为我们定义bf采用右边减左边),当父母节点bf改变后,如果绝对值为1,那么需要继续向上调整,调整的规则也是,看该节点在其父母节点的左边还是右边,左边则--,右边则++,直到遇到每次调整parent时,parent的bf变为0,则说明不需要调整了,并且插入成功,又或者调整parent时,其bf绝对值等于2,则说明需要对该部分子树进行旋转调整了(降高度)

旋转调整

旋转又分成四种方式,分别是左单旋、右单旋、左右双旋、右左双旋

1.单旋

左单旋,当遇到这种最右边高左边低的时候,采用左单旋,将60位置的左子树给30位置,再由60去取代30原先的位置,形象点看就是,好像吧一个旋钮往左旋转了一样

右单旋,同理就是与左单旋的情况相反,最左边高,右边低,将30的b给60,30取代60的位置

代码实现

从画图上来看,这就是左单旋和右单旋,看似简单,但在转化成代码的时候,要注意几个问题:

1.要注意每个节点的parent指针需要被维护

2.一开始我们就对需要用到的位置,用指针去一 一对应的话,要注意中间的那个b是有可能为空的

3.最上面的位置不一定就是根节点,因此在更换掉最上面位置节点的时候,要考虑对其上面是否还有节点进行判断,如果还有节点,则进行进一步链接,为根节点则需要将其parent置空

右单旋同理,需要注意的细节和左旋是一样的,这里给出左单旋和右单旋的代码参考

左单旋代码参考:

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
		else
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		parent->_bf = subR->_bf = 0;
	}

右单旋代码参考:

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
		else
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		parent->_bf = subL->_bf = 0;
	}
2.双旋

双旋是为了解决子树往中间偏的问题,当子树往中间偏高时,可以先将其往一边掰直,转换成上面两种情况之一,然后再调整,因为需要两次单旋操作,所以叫双旋

左右双旋,对30位置进行左旋,然后再对90位置进行右旋,从结果来看就是,将60最终放到最上面,60的左子树给左边的30,60的右子树给了右边90

右左双旋同理

双旋的代码实现:

双旋的难点不是在于实现双旋操作本身,因为只需要复用左单旋和右单旋就可以了,真正的难点在于对平衡因子的更新,与单旋不同,双旋对平衡因子的更新并不是直接将调整位置的值都变成0,而是需要根据实际情况去分类讨论的

拿左右双旋举例

左右双旋代码分析

代码参考:

左右双旋:

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int subLR_bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);
		if (subLR_bf == -1)//在b下新增节点
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (subLR_bf == 1)//在c下新增节点
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (subLR_bf == 0)//subRL就是新增节点
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_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 == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
3.根据不同情况选择不同旋转方式

以上,就是AVL树的核心实现,最重要的就是控制平衡,通过旋转调整平衡的方式,保证了每个节点的左右子树绝对值不超过1,实现平衡,大大加快了搜索二叉树的效率,搜索效率能够达到
O(logN)

4.测试接口

中序遍历

中序遍历有序只能保证树是二叉搜索树,但不能保证平衡

	void _InOrder()
	{
		InOrder(_root);
		cout << endl;
	}
	void InOrder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		InOrder(root->_left);
		cout << root->_value.first << " ";
		InOrder(root->_right);
	}

验证平衡因子是否绝对值不超过1

这里提供一个IsBalanceTree接口验证是否平衡因子绝对值不超过1

	int _Height()
	{
		return Height(_root);
	}
	int Height(const Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = Height(root->_left) + 1;
		int right = Height(root->_right) + 1;
		return left > right ? left:right;
	}
	bool _IsBalanceTree()
	{
		return IsBalanceTree(_root);
	}
	bool IsBalanceTree(Node* root)
	{
		// 空树也是AVL树
		if (nullptr == root) 
			return true;

		// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
 // pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (diff != root->_bf || (diff > 1 || diff < -1))
			return false;
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return IsBalanceTree(root->_left) && IsBalanceTree(root -> _right);
	}

随机值插入验证测试

bool test()
{
	//16, 3, 7, 11, 9, 26, 18, 14, 15
	//4, 2, 6, 1, 3, 5, 15, 7, 16, 14
	srand(time(0));
	const size_t N = 1000;
	AVLTree<int, int> n;
	for (int i = 0; i < N; i++)
	{
		size_t x = rand()%100;
		n.Insert(make_pair(x, x));
	}

	n._InOrder();

	return n._IsBalanceTree();
}

总结

本篇内容整理总结了AVL树的核心实现,分析了其中的内部原理,对原理以及实现都画图进行了分析,提供了源码参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/142311.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android问题笔记四十四:关于RecyclerView出现Inconsistency detected崩溃

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

Linux---(六)自动化构建工具 make/Makefile

文章目录 一、make/Makefile二、快速查看&#xff08;1&#xff09;建立Makefile文件&#xff08;2&#xff09;编辑Makefile文件&#xff08;3&#xff09;解释&#xff08;4&#xff09;效果展示 三、背后的基本知识、原理&#xff08;1&#xff09;如何清理对应的临时文件呢…

vite 深入浅出

vite 深入浅出 简介 vite(轻量&#xff0c;轻快的意思) 是一个由原生 ES Module 驱动的 Web 开发前端构建工具。 浏览器原生 ESM&#xff1a;浏览器支持的 JavaScript 模块化标准&#xff0c;可以直接使用 <script type"module"> 标签加载模块&#xff0c;无…

第二证券:定增价公布后第二天股价表现?

近年来&#xff0c;定增成为一种较为老练的公司融资方法&#xff0c;它通过向指定政策定向发行股份来筹集资金&#xff0c;相关于非公开发行股票或增发股份&#xff0c;定增的市场轰动和价格变化相对较小。但是&#xff0c;定增股票发行通常会推动股价的不坚决和出资者的心境崎…

Prometheus+Ansible+Consul实现服务发现

一、简介 1、Consul简介 Consul 是基于 GO 语言开发的开源工具&#xff0c;主要面向分布式&#xff0c;服务化的系统提供服务注册、服务发现和配置管理的功能。Consul 提供服务注册/发现、健康检查、Key/Value存储、多数据中心和分布式一致性保证等功能。 在没有使用 consul 服…

【社会网络分析第5期】gephi使用指南

gephi数据可视化 gephi数据可视化1、软件安装2、数据处理与导入&#xff08;1&#xff09;导入节点&#xff08;2&#xff09;导入边&#xff08;3&#xff09;改变节点的颜色&#xff08;4&#xff09;根据pagerank调整节点的大小&#xff08;5&#xff09;根据pagerank调整边…

上海亚商投顾:沪指缩量调整跌 高位强势股继续退潮

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数11月10日弱势震荡&#xff0c;上证50盘中跌超1%&#xff0c;以保险为首的权重板块走势较弱。 高位强…

SpringCloudalibaba

一、分布式和微服务 分布式系统和服务是现代软件开发中的两个重要概念。它们为复杂的应用程序提供了模块化和可扩展性&#xff0c;使其能够在多台机器上运行&#xff0c;并为大量用户提供服务。 分布式系统 定义: 分布式系统是由多个独立组件组成的系统&#xff0c;这些组件…

Maven 插件统一修改聚合工程项目版本号

目录 引言直接修改 pom.xml 的版本号的问题Maven 插件修改版本号开源项目微服务商城项目前后端分离项目 引言 在Maven项目中&#xff0c;我们通常有两种常见的方式来修改版本号&#xff1a;直接在pom.xml文件中手动编辑和利用Maven插件进行版本号调整。 本文将比较这两种修改…

R语言编写代码示例

R语言编写的爬虫程序&#xff0c;使用了requests库来发送请求&#xff0c;使用BeautifulSoup库来解析HTML。 r # 第一步&#xff0c;安装必要的库 install.packages("xml2") install.packages("requests") install.packages("httr") install.pac…

【系统安装】ubuntu20.04安装,正经教程,小白安装教程,百分百成功安装

1、安装的前提是有启动盘&#xff0c;这个比较好处理&#xff0c;清华源找到ubuntu20.04.iso镜像文件下载&#xff0c;然后用Rufus来制作启动盘就可以了&#xff0c;需要注意的是目标文件系统需要是UEFI&#xff0c;其他的话就没太多要求了&#xff0c;如果卡在这一步的话&…

助力燃气安全运行:智慧燃气管网背景延展

关键词&#xff1a;城市燃气管网、智慧燃气管网、智慧管网、智慧燃气管网解决方案、智慧燃气 01背景 当前&#xff0c;随着我国城市化进程不断加快&#xff0c;城市燃气管网也不断延伸&#xff0c;运行规模庞大&#xff0c;地下管线复杂&#xff0c;不少城市建设“重地上轻地…

Windows系统下使用docker部署redis

使用虚拟机部署redis&#xff0c;虚拟机很占用电脑资源&#xff0c;所以选择使用docker对redis进行部署。 一、安装docker 安装链接&#xff1a;https://docker.p2hp.com/ 二、配置redis.conf文件 下载配置文件&#xff1a;https://download.redis.io/redis-stable/redis.con…

Js 语句

JavaScript 语句向浏览器发出的命令&#xff0c;语句的作用是告诉浏览器该做什么&#xff1b;分号用于分隔 JavaScript 语句&#xff0c;通常我们在每条可执行的语句结尾添加分号&#xff1b;使用分号的另一用处是在一行中编写多条语句。 JavaScript 语句通常以一个 语句标识符…

【C语言】深入解开指针(二)

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 &#x1f308;作者寄语 &#x1f308;&#xff1a; 小菜鸟的力量不在于它的体型&#xff0c;而在于它内心的勇气和无限的潜能&#xff0c;只要你有决心&#xff0c;就没有什么事情是不可能的…

Hubspot是如何发展到今天的?有哪些实用工具?

HubSpot&#xff0c;作为一家全球领先的数字化市场营销和销售平台提供商&#xff0c;通过其强大的生态圈和创新的解决方案&#xff0c;帮助企业实现高效运营、客户吸引和业务增长。运营坛今天将详细介绍HubSpot的发展历程以及其三大核心产品&#xff1a;CMS Hub、Marketing Hub…

雷达波形及MATLAB仿真

文章目录 前言一、雷达波形二、Matlab 仿真1、SFW 的距离分辨率和距离模糊①、MATLAB 源码②、仿真结果 三、资源自取 前言 本文对雷达波形的内容以思维导图的形式呈现&#xff0c;有关仿真部分进行了讲解实现。 一、雷达波形 思维导图如下图所示&#xff0c;如有需求请到文章…

No198.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

@Async注解的坑

问题描述 一个方法调用另一个方法(该方法使用Async注解)在同一个类文件中&#xff0c;该注解会失效&#xff01; 问题复现 TestAsyncController 类 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; im…

社区新零售:改变生活方式的创新商业模式

社区新零售&#xff1a;改变生活方式的创新商业模式 社区新零售&#xff0c;顾名思义&#xff0c;以社区为核心&#xff0c;利用互联网、大数据、人工智能等先进技术&#xff0c;将线上购物和线下体验有机结合&#xff0c;形成一种全新的零售模式。它特别强调地理位置的便利性&…