【1++的数据结构】之AVL树

news2024/11/19 21:27:00

👍作者主页:进击的1++
🤩 专栏链接:【1++的数据结构】


文章目录

  • 一,什么是AVL树
  • 二,AVL树的插入
  • 三,AVL树的旋转
    • 3.1 向左旋转
    • 3.2 向右旋转
    • 3.3 左右双旋
    • 3.4 右左双旋
  • 四,验证AVL树是否平衡

一,什么是AVL树

在说AVL树之前我们先来说说为什么会出现AVL树。在前面的文章中我们讲过二叉搜索树,虽然查找,插入效率比较高,但其有个缺陷:在某些情况下其可能会成为一颗单支树或其他高度较高的树,这时我们的效率就比较低了,甚至接近于O(n)。在此背景下,有两位俄罗斯数学家,便发明了AVL树----当插入新的结点后,保证每个结点的左右子树的高度差不大于1。则这颗树就会接近满二叉树,其高度就会降低,那么查找,插入效率也会提高。
在这里插入图片描述

二,AVL树的插入

在说AVL树的插入之前我们先来了解其结点。与二叉搜索树不同,其是三叉链结构和平衡因子,不但有左右指针,还有指向双亲结点的指针。这么设计的好处在于后面更新平衡因子是会比较方便。
我们来看结点结构代码:

template<class K, class V>
	struct AVLTreeNode
	{
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		pair<K, V> _kv;
		int _bf;//平衡因子
		AVLTreeNode(const std::pair<K, V>& kv)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _kv(kv)
			, _bf(0)
		{}

	};

了解了结点的结构后,我们就可以进行插入操作的学习。
与二叉搜索树一样,其先要找到要插入的位置,代码如下;

if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}
			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->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

与以前不同的是,由于是三叉链结构,因此当找到位置后,我们还要与其双亲结点进行链接。
当找到合适的位置进行插入后,便要进行控制平衡,使每个结点的左右子树高度差不超过1。

每个根节点的平衡因子的计算过程为;若插入的结点在其左侧,则根节点的平衡因子-1;若在右侧,则平衡因子+1。
根据平衡因子,我们总结出了三种控制平衡的规律:

  1. 若插入后根节点的平衡因子变为0,则说明在插入前其平衡因子为正负1,插入后被调整为0,满足AVL树的性质,则不做任何操作。
  2. 若插入后根节点的平衡因子变为正负1,则说明在插入前其平衡因子为0,插入后被改变,则向上继续更新,直到根节点。
  3. 若插入后平衡因子变为正负2,则违反了AVL树平衡的性质,需要进行旋转处理。

旋转操作,我们会在后面进行说明。

以下是控制其平衡的代码:

while (parent)
			{
				if (cur == parent->_right)
				{
					parent->_bf++;
				}
				else
				{
					parent->_bf--;
				}

				if (parent->_bf == 0)
				{
					break;
				}
				else if (abs(parent->_bf) == 1)
				{
					parent = parent->_parent;
					cur = cur->_parent;
				}
				else if (abs(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);
					}
					
						break;


				}
				else
				{
					assert(false);
				}
			}
		}

三,AVL树的旋转

3.1 向左旋转

以下是向左 旋转的代码:

void RotateL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR ->_left;
			parent->_right = SubRL;
			if (SubRL)
			{
				SubRL->_parent = parent;
			}

			Node* ppNode = parent->_parent;
			SubR->_left = parent;
			parent->_parent = SubR;
			if (_root == parent)
			{
				_root = SubR;
				SubR->_parent = nullptr;
			}
			else
			{
				if (ppNode->_left == parent)
				{
					ppNode->_left = SubR;
				}
				else
				{
					ppNode->_right = SubR;
				}
				SubR->_parent = ppNode;

			}
			SubR->_bf = parent->_bf = 0;

		}

那么在什么情况下才会进行向左旋转呢?
在这里插入图片描述

在这里插入图片描述

如图,当我们的新插入的结点在较高子数的右侧时,此时30这个结点的平衡因子变为了2,满足了旋转的条件(图中h可为任何正整数或0)我们可以从图中清楚的看到,当插入新节点后平衡因子为2的结点的右子数明显高,为了平衡,我们则需要给其右子树将高度,我们将30称为parent,60称为SubR;60的左子树b称为SubRL。从图中我们看到,SubR成为了这颗树或者说是子树的新结点。而SubRL成为了parent的右子树。改变后仍遵循二叉搜索树的规则。此时,我们的左旋转就完成了。旋转后,平衡因子也发生了改变,所以要对平衡因子进行更新。对parent和SubR的平衡因子进行更新,更新后都变为0。

3.2 向右旋转

向右旋转与向左旋转相似
代码如下:

void RotateR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;
			parent->_left = SubLR;
			if (SubLR)
			{
				SubLR->_parent = parent;
			}
			Node* ppNode = parent->_parent;
			SubL->_right = parent;
			parent->_parent = SubL;
			if (_root == parent)
			{
				_root = SubL;
				SubL->_parent = nullptr;
			}
			else
			{
				if (ppNode->_left == parent)
				{
					ppNode->_left = SubL;
				}
				else
				{
					ppNode->_right = SubL;
				}

				SubL->_parent = ppNode;
			}
			SubL->_bf = parent->_bf = 0;

		}

向右旋转的情况如下图:
在这里插入图片描述
当新插入的结点在较高左子树的左侧时,进行右旋转。
如图,我们可以看出其左子树高,我们仍将60称为parent,将30称为SubL,将30的右子树称为SubLR。旋转时,将SubL作为了根节点,SubLR给了parent的左侧。旋转完成后要进行平衡因子的更新,parent与SubL都更新为0。

3.3 左右双旋

代码如下:

void RotateLR(Node* parent)
		{
			Node* SubL = parent->_left;
			Node* SubLR = SubL->_right;
			int bf = SubLR->_bf;
			RotateL(parent->_left);
			RotateR(parent);
			SubLR->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = 0;
				SubL->_bf = -1;
			}
			else if (bf == -1)
			{
				parent->_bf = 1;
				SubL->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				SubL->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}

当新结点插入较高左子树的右侧时,则会进行左右双旋,就是先向左旋转,再向右旋转。
在这里插入图片描述
先以30为parent进行左旋转,再以90为parent进行右旋转。
左右旋转我们在上面已经进行了讲述,接下来,我们的重点是平衡 因此的更新,在上图这种情况下,新插入的结点可以为60的左子树,也可以为右子树,或者60本身就为新插入的结点。
因此在不能的情况下,平衡因子的更新也不同。
主要有以下情况:
(SubL为30,parent为90)

			Node* SubL= parent->_left;
			Node* SubLR = SubL->_right;
			int bf = SubLR->_bf;
			SubLR->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = 0;
				SubL->_bf = -1;
			}
			else if (bf == -1)
			{
				parent->_bf = 1;
				SubL->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				SubL->_bf = 0;
			}

3.4 右左双旋

代码如下:

void RotateRL(Node* parent)
		{
			Node* SubR = parent->_right;
			Node* SubRL = SubR->_left;
			int bf = SubRL->_bf;
			RotateR(parent->_right);
			RotateL(parent);
			SubRL->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = -1;
				SubR->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				SubR->_bf = 1;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				SubR->_bf = 0;
			}
			else
			{
				assert(false);
			}

		}

当新结点插入在较高右子树的左侧时,进行右左双旋(先向右旋转,在向左旋转)。
在这里插入图片描述
与左右双旋相似,其平衡因子的更新也有几种情况:

SubRL->_bf = 0;
			if (bf == 1)
			{
				parent->_bf = -1;
				SubR->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				SubR->_bf = 1;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				SubR->_bf = 0;
			}
			else
			{
				assert(false);
			}

四,验证AVL树是否平衡

AVL树最重要的一个性质就是其每个结点的左右子树之差的绝对值小于2。
并且等于该结点的平衡因子的绝对值。
我们以此来测试我们所写的AVL树是否正确
代码如下:


//求该子树的高度
int Height(Node* root)
		{
			if (root == nullptr)
			{
				return 0;
			}
			return max(Height(root->_left), Height(root->_right)) + 1;
				
		}

bool _Isbalance(Node* root)
		{
			if (root == nullptr)
			{
				return true;
			}
			int leftHT = Height(root->_left);
			int rightHT = Height(root->_right);
			int diff = rightHT - leftHT;
			if (diff != root->_bf)
			{
				cout << root->_kv.first << "平衡因子异常" << endl;
				return false;
			}
			return abs(diff) < 2 
				&& _Isbalance(root->_left)
				&& _Isbalance(root->_right);

		}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端学习之轮播图

前端学习之轮播图 该案例涉及到定时器的使用&#xff0c;元素的活获取&#xff0c;函数的调用等知识的运用 显示图如下&#xff1a;可以点击图标跳转图片&#xff0c;也可以自动轮播 源码如下 <!DOCTYPE html> <html><head><meta charset"UTF-8&q…

监控抽烟检测识别算法

监控抽烟检测识别算法采用yolov7系列网络模型深度学习图像识别技术&#xff0c;监控抽烟检测识别算法能够准确识别人员抽烟的动作和烟雾&#xff0c;监控抽烟检测识别算法一旦发现有人员在禁烟区域内抽烟&#xff0c;将立即触发预警。YOLO的结构非常简单&#xff0c;就是单纯的…

正中优配:“核污染防治”炒作按下暂停键, 中电环保、建龙微纳大跌

连日大涨的核污染防治炒作“步伐”放缓。 8月29日上午&#xff0c;核污染防治概念股多数低开&#xff0c;截止到午间休市&#xff0c;此前4个交易日累计涨超80%的中电环保&#xff08;300172.SZ&#xff09;大跌6.76%。 8月28日晚间&#xff0c;中电环保发布异动公告&#xff…

遗传算法决策变量降维的matlab实现

1.案例背景 1.1遗传算法概述 遗传算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。它最初由美国Michigan大学的J. Holland教授提出,1967年, Holland 教授的学生 Bagley在其博士论文中首次提出了“遗传…

三电平离网逆变器接不平衡负载仿真

文章目录 **0、系统框图****1、纯阻性负载R****2、纯感性负载L****3、纯容性负载C****4、 RL负载****5、RC负载****6、CL负载****7、RLC负载** 0、系统框图 闭环控制 PWM调制 逆变桥 LCL滤波电路 负载&#xff08;可配置&#xff09; 坐标变换&#xff08;采样得到&#xff09;…

SAP_ABAP_FUNCTION_ALV案例

SAP ABAP顾问能力模型梳理_企业数字化建设者的博客-CSDN博客SAP Abap顾问能力模型https://blog.csdn.net/java_zhong1990/article/details/132469977 一、Function ALV 1.1 基于退货采购订单创建&#xff0c;解释 FUNCTION_ALV开发的程序结构与代码模板参考 1.2 程序结构 to…

ORA-00604 ORA-00069报错

在测试环境上删除用户&#xff0c;报错如下 rop user "USR_EOS" cascade; * ERROR at line 1: ORA-00604: error occurred at recursive SQL level 1 ORA-00069: cannot acquire lock -- table locks disabled for T_EMPLOYEE 解决方法 alter table USR_EOS.T_EMPL…

运维架构师:驱动企业运维向高效发展“

运维架构师是高级运维工程师的角色&#xff0c;主要负责运维工作的策划和执行。他们需要熟练掌握开源工具&#xff0c;但更重要的是运用思维来进行运维工作&#xff0c;实现DevOps理念和解决各种企业运营中的挑战。他们的职责包括设计各种运维解决方案&#xff0c;例如自动化代…

Python框架【模板继承、继承模板实战、装饰器、蓝图(介绍、单文件、目录结构、模版文件、静态文件 url_for函数子域名实现)】(五)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

深入解析SNMP协议及其在网络设备管理中的应用

SNMP&#xff08;Simple Network Management Protocol&#xff0c;简单网络管理协议&#xff09;作为一种用于网络设备管理的协议&#xff0c;在实现网络设备的监控、配置和故障排除方面发挥着重要的作用。本文将深入解析SNMP协议的工作原理、重要概念和功能&#xff0c;并探讨…

回首一路坎坷,献给刚入测试的你

恍惚间&#xff0c;已经进入测试岗位四年多的时间了&#xff0c;回顾过往&#xff0c;思绪良多&#xff0c;一路走来&#xff0c;或多或少的经历了一些坎坷&#xff0c;也遇到了不少的困难&#xff0c;也曾四顾迷茫&#xff0c;在此&#xff0c;我结合了自己的一些心得&#xf…

【实训项目】精点考研

1.设计摘要 如果说高考是一次能够改变命运的考试&#xff0c;那么考研应该是另外一次。为什么那么多人都要考研呢&#xff1f;从中国教育在线官方公布是考研动机调查来看&#xff0c;大家扎堆考研的原因大概集中在这6个方面&#xff1a;本科就业压力大&#xff0c;提升竞争力、…

C++ 面试题(一)--C++基础,面向对象,内存管理

目录 1.part1 C基础 1 C特点 2 说说C语言和C的区别 3 说说 C中 struct 和 class 的区别 4 include头文件的顺序以及双引号""和尖括号<>的区别 5 说说C结构体和C结构体的区别 6 导入C函数的关键字是什么&#xff0c;C编译时和C有什么不同&#xff1f; 7…

Tomcat安装及基本使用

1. 什么是Web服务器 Web服务器是一种应用程序&#xff08;软件&#xff09;&#xff0c;它封装了对HTTP协议的操作&#xff0c;使得开发人员无需直接操作协议&#xff0c;从而简化了Web开发。其主要功能是提供网上信息浏览服务。 Web服务器安装在服务器端&#xff0c;我们可以…

JavaWeb 速通JSON

目录 一、JSON快速入门 1.基本介绍 : 2.定义格式 : 3.入门案例 : 二、JSON对象和字符串的相互转换 1.常用方法 : 2.应用实例 : 3.使用细节 : 三、JSON在Java中的使用 1.基本说明 : 2.应用场景 : 2.1 JSON <---> JavaBean 2.2 JSON <---> List 2.3 JSON …

java对时间序列根据阈值进行连续性分片

问题描述&#xff1a;我需要对一个连续的时间戳list进行分片&#xff0c;分片规则是下一个数据比当前数据要大于某一个阈值则进行分片&#xff1b; 解决方式&#xff1a; 1、输入的有顺序的list &#xff0c;和需要进行分片的阈值 2、调用方法&#xff0c;填入该排序的list和阈…

非煤矿山风险监测预警算法 yolov8

非煤矿山风险监测预警算法通过yolov8网络模型深度学习算法框架&#xff0c;非煤矿山风险监测预警算法在煤矿关键地点安装摄像机等设备利用智能化视频识别技术&#xff0c;能够实时分析人员出入井口的情况&#xff0c;人数变化并检测作业状态。YOLO的结构非常简单&#xff0c;就…

element-ui el-upload组件 on-remove事件 传自定义参数

element-ui el-upload组件 on-remove事件 传自定义参数 1.vue页面 :on-remove"(file, fileList) > {handleRemove(file, fileList, item.order)}"2.methods方法里面

promethues监控postgres,emqx,redis

一、监控postgres 1、安装监控 docker pull wrouesnel/postgres_exporter2、执行 docker run -d -p 9187:9187 --name postgres_exporter --nethost -d -e DATA_SOURCE_NAME"postgresql://postgres:123456192.168.12.116:5432/rcc-manage?sslmodedisable" wroues…

移动隔断墙,高隔间将是一种十分理想的空间划分装饰

移动隔断墙&#xff0c;高隔间是一种非常理想的空间划分装饰方式。它可以根据需要随时移动和重新布置&#xff0c;提供灵活的空间解决方案。高隔间可以用于划分办公区域、会议室、展示区、休息区等不同功能的空间&#xff0c;使整个空间更加有序和高效。 高隔间的设计具有多样性…