AVL树的学习

news2024/9/20 1:01:47

1.1 AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右
子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均
搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1),记住平衡因子的概念
1.2 AVL树的定义:
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;//平衡因子
};

当然我们也可以不把数值存成pair的形式,这样是和map一致,各有各的用处

AVLTreeNode(const pair<K, V>& kv)
	:_kv(kv)
	,left(nullptr)
	,right(nullptr)
	,parent(nullptr)
	,_bf(0)
{

}

我们用初始化列表的方式完成构造函数,也不算完成吧,因为这个是需要参数的构造函数,这个应该叫做赋值拷贝。

1.3 AVL树的插入

主要分为两步:1. 按照二叉搜索树的方式插入新节点 2. 调整节点的平衡因子

看着简单实则不太容易。

(此截图来着bili的up主:蓝不过海呀) 

主要就是用来失衡怎么做

首先是右单旋,也就是出现此情况的时候,我们要进行右旋,,也就是形成该样子,把平衡因子为2的14结点向下转,也就是右旋,转成六结点的右孩子,然后6的右孩子,就成了14的左孩子,6的右孩子是空也无妨。

void RotateR(Node* parent)
{
	Node* subL = parent->left;
	Node* subLR = subL->right;

	parent->left = subLR;
	if (subLR)
		subLR->parent = parent;

	Node* PParent = parent->parent;
	subL->right = parent;
	parent->parent = subL;
	if (PParent == nullptr)
	{
		root = subL;
		
	}
	else
	{
		if (PParent->right == parent)
			PParent->right = subL;
		else
			PParent->left = subL;
	}
	subL->parent = PParent;
	subL->_bf = parent->_bf = 0;
}

我们把失衡的结点也就是图中的14,传过来,传给parent。由于我们刚才分析过了,会改变6和6的右孩子,所以我们记录此时这几个结点,然后进行改变,最后6顶替了14的位置,如果原来14有父亲的话,要让6成为原来14父亲的孩子,进行重新链接

左旋就是同样的道理

void RotateL(Node* parent)
{
	Node* subR = parent->right;
	Node* subRL = subR->left;

	parent->right = subRL;
	if (subRL)
		subRL->parent = parent;

	Node* PParent = parent->parent;
	subR->left = parent;
	parent->parent = subR;

	if (PParent)
	{
		subR->parent = PParent;
		if (PParent->left == parent)
			PParent->left = subR;
		else
			PParent->right = subR;
	}
	else
	{
		subR->parent = nullptr;
		root = subR;
	}
	//平衡因子
	subR->_bf = parent->_bf = 0;
		
}

 我们再代码区域看到了,我已经进行了平衡因子的更新,接下来我们说一下单旋的平衡因子是怎么更新的。我们以左单旋为例子。

,不要纠结为什么是h啥的高度,只是为了更好的计算平衡因子,相当于数学的代入字母证明的一般式,所以我们可以得到,如果单选的话,parent和parent->left或者right 的平衡因子都更新成0。

接着我们来解决另外两种双旋的,举例子我们用先左旋再右旋的,也就是LR型

先进行第一次旋转,这是一次左旋,接上的话,,就又成为了应该刚才右旋的,因为此时的平衡因子是-2 和-2.总结一下,就是失衡的结点再次成为parent ,然后parent的left要往下转,也就是左旋,然后成为它右孩子的子结点,也就是成为parent->left->right的子结点,正好对应了LR型,然后进行右旋,使得parent->left->right成为新的此时的“根”结点。

void RotateLR(Node* parent)
{
	Node* subL = parent->left;
	Node* subLR = subL->right;
	int bf = subLR->_bf;
	RotateL(parent->left);
	RotateR(parent);
	
	if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else
		assert(false);


}
void RotateRL(Node* parent)
{
	Node* subR = parent->right;
	Node* subRL = subR->left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);
	
	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else 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
		assert(false);
}

我们可以看到,我们的旋转用的之前写好的左单旋和右单旋,然后我们只需要更新平衡因子就行了。其实双旋的平衡因子才是麻烦事我们再次用一般形式来进行计算。最后我们做出总结:只要看 第三个结点,也就是parent->left->right 或者事 parent->right->left 的平衡因子分为三种情况,大家可以多画画图就知道代码写的意思了。

两种种就是以上两张图片的情况,

第一种是为平衡因子为1的情况,

第二种是为-1的。都是分别进行左右双旋。

最后一种就是是插入之后,它的平衡因子为零,那么改动的三个结点:parent parent->left  parent->left->right(parent parent->right parnet->right->left) 的平衡因子都要成为0;

以上霓虹颜色的截图都来自于up主蓝不过海呀 的视频。

最后分享完整代码

		bool insert(const pair<K, V>& kv)
		{
			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->left;
				}
				else if (cur->_kv.first < kv.first)
				{
					parent = cur;
					cur = cur->right;
				}
				else
				{
					cout << "已经重复了" << endl;
					return false;
				}
			}
			cur = new Node(kv);
			if (parent->_kv.first > cur->_kv.first)
			{
				parent->left = cur;
			}
			else
			{
				parent->right = cur;
			}
			cur->parent = parent;
			//更新平衡因子
			while (parent)
			{
				if (cur == parent->left)
				{
					parent->_bf--;
				}
				else
				{
					parent->_bf++;
				}
				if (parent->_bf == 0)
				{
					break;
				}
				else if (parent->_bf == -1 || parent->_bf == 1)
				{
					//祖先需要更新,所以父亲和cur网上挪,然后再次循环就会到2的条件
					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
					{
						RotateRL(parent);
					}
					break;
				}
				else
				{
					assert(false);
				}
			}
			return true;
		}

		void RotateL(Node* parent)
		{
			Node* subR = parent->right;
			Node* subRL = subR->left;

			parent->right = subRL;
			if (subRL)
				subRL->parent = parent;

			Node* PParent = parent->parent;
			subR->left = parent;
			parent->parent = subR;

			if (PParent)
			{
				subR->parent = PParent;
				if (PParent->left == parent)
					PParent->left = subR;
				else
					PParent->right = subR;
			}
			else
			{
				subR->parent = nullptr;
				root = subR;
			}
			//平衡因子
			subR->_bf = parent->_bf = 0;
				
		}
		
		void RotateR(Node* parent)
		{
			Node* subL = parent->left;
			Node* subLR = subL->right;

			parent->left = subLR;
			if (subLR)
				subLR->parent = parent;

			Node* PParent = parent->parent;
			subL->right = parent;
			parent->parent = subL;
			if (PParent == nullptr)
			{
				root = subL;
				
			}
			else
			{
				if (PParent->right == parent)
					PParent->right = subL;
				else
					PParent->left = subL;
			}
			subL->parent = PParent;
			subL->_bf = parent->_bf = 0;
		}
		void RotateLR(Node* parent)
		{
			Node* subL = parent->left;
			Node* subLR = subL->right;
			int bf = subLR->_bf;
			RotateL(parent->left);
			RotateR(parent);
			
			if (bf == 0)
			{
				subL->_bf = 0;
				subLR->_bf = 0;
				parent->_bf = 0;
			}
			else if (bf == 1)
			{
				subLR->_bf = 0;
				subL->_bf = -1;
				parent->_bf = 0;
			}
			else if (bf == -1)
			{
				subLR->_bf = 0;
				subL->_bf = 0;
				parent->_bf = 1;
			}
			else
				assert(false);


		}
		void RotateRL(Node* parent)
		{
			Node* subR = parent->right;
			Node* subRL = subR->left;
			int bf = subRL->_bf;
			RotateR(subR);
			RotateL(parent);
			
			if (bf == 0)
			{
				subR->_bf = 0;
				subRL->_bf = 0;
				parent->_bf = 0;
			}
			else 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
				assert(false);
		}

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

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

相关文章

微前端架构:使用不同框架构建可扩展的大型应用

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言 在现代前端开发中&#xff0c;随着应用规模的扩大和团队分工的复杂化&#xff0c;传统的单体前端架构逐渐暴露出维护困难、部署周期长、技术栈更新不便等问题。为了应对这些挑战&#xff0c;微前端架构应运而生。这种架构…

每日OJ_牛客_树根(简单模拟)

目录 牛客_树根&#xff08;简单模拟&#xff09; 解析代码 牛客_树根&#xff08;简单模拟&#xff09; 数根__牛客网 解析代码 这个题目很容易理解&#xff0c;对于数字的每一位进行相加直到不大于9为止即可。 接收字符串得到各个数字&#xff0c;并且每位求和&#xff…

使用yolov5实现目标检测简单案例(测试图片)

一、前置 测试这个案例之前需要安装一些前置的东西&#xff0c;如果已经安装的可以忽略&#xff0c;下面我给出我跟着做的一些很好的博客提供大家参考&#xff0c;因为我们主要目的还是实现yolov5的目标检测。 1、安装nvidia显卡驱动 可以参考&#xff1a;【Windows】安装NV…

从ARM的最强超大核处理器来看:Arm Core里都有啥?

目录 L1 instruction memory systemInstruction decode -指令译码Register rename -寄存器重命名Instruction issue - 指令发射Integer execute - 整数执行Vector execute - 向量执行Advanced SIMD and floating-point support - 高级 SIMD 和浮点支持Cryptographic Extension …

在亚马逊云科技上部署开源大模型并利用RAG和LangChain开发生成式AI应用

项目简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次介绍的是如何在亚马逊云科技上利用Sag…

CMake 的快速应用

一&#xff0c;什么是CMake&#xff1f; 我们在学习了C 和 C后&#xff0c; 知道从c/c代码,变成可执行文件的过程如下&#xff1a; 如果使用的IDE开发&#xff0c;例如vs 2017&#xff0c;QT,等&#xff0c;那么一般IDE会帮你生成可执行文件&#xff0c;开发者一键点击&#…

Wandb使用指南

安装&#xff1a; pip install wandb 登录 wanbd login 在terminal中操作查看你的API key并粘贴回车进行授权&#xff08;https://wandb.ai/authorize&#xff09; 设置离线模式/在线模式 设置为offline会在无网络&#xff08;内网&#xff09;的时候使用&#xff0c;常用于de…

如何运用独特的产业运营体系打造一流的数字媒体产业园

如何运用独特的产业运营体系打造一流的数字媒体产业园 2024-08-15 17:37树莓集团 在数字经济蓬勃发展的今天&#xff0c;数字媒体产业作为其中的重要一环&#xff0c;正展现出巨大的潜力和活力。而如何运用独特的产业运营体系&#xff0c;打造一流的数字媒体产业园&#xff0…

SQLAlchemy 学习笔记

通信类型&#xff1a;AF_INET 协议家族一般是表示TCP通信的SOC_STREAM和UDP通信的SOCK_DGRAM。对于TCP通信&#xff0c;建立socket连接&#xff0c;&#xff1a; s socket.socket(socket.AF_INET, socket.SOCK_STREAM)连接socket&#xff0c; s.connect((host,port))socket通信…

PostWigger的xss漏洞

文章目录 Lab: Exploiting DOM clobbering to enable XSS Lab: Exploiting DOM clobbering to enable XSS 这是一道dom破坏题。 首先进入&#xff0c;发现都是一个个博客。 随便点击看看。 发现是一篇文章之后是一些评论以及咱们也可以发布评论。这里的Email使用了html的正…

Redis的缓存淘汰策略

1. 查看Redis 最大的占用内存 打开redis配置文件, 设置maxmemory参数&#xff0c;maxmemory 是bytes字节类型, 注意转换 2. Redis默认内存多少可以用 注意: 在64bit系统下&#xff0c; maxmemory 设置为 0 表示不限制Redis内存使用 3. 一般生产上如何配置 一般推荐Redis 设置内…

微信小程序骨架屏

骨架屏是常用的一种优化方案&#xff0c;针对于页面还未加载完时给用户的一种反馈方式。如果自己要写骨架屏有点复杂因为页面的元素过多且不稳定&#xff0c;这边直接使用微信开发工具生成骨架屏。也不只有微信开发工具有像常用的抖音开发工具&#xff0c;字节开发工具都有对应…

Python自准直仪双筒望远镜光学ABCD矩阵行为算法

&#x1f3af;要点 &#x1f3af;平面&#xff1b;曲面&#xff1b;圆柱面&#xff1b;非球面光&#xff0c;双凸透镜&#xff1b;90 度棱镜&#xff1b;分束立方体&#xff0c;双透镜棱&#xff1b;镜分光镜光线&#xff1b;横置隔膜&#xff1b;全内反射&#xff1b;多个分束…

【Django开发】前后端分离django美多商城项目第1篇:欢迎来到美多 项目主要页面介绍【附代码文档】

本教程的知识点为&#xff1a; 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计&#xff1a; 视图原型 2. 具体视图实现 用户部分 使用Celery完成发送 判断帐号是否存在 1. 判断用户名是否存在 后…

看图学sql之sql 中的窗口函数

数据分析社区直达 免费数据分析资料下载。定期分享数据分析领域的最新动态、实战案例、技术工具评测、数据可视化技巧以及行业洞察报告。

【Arduino】ATmega328PB 单片机初始化配置,连接使用配置 arduino

总览 1.下载资料 2.配置 arduino 首选项 3.配置开发板管理器 4.配置不同 晶振频率 的 mega328PB 的参数设置 一、下载资料 1.你也可以看着资料自己来弄&#xff0c;如果嫌我麻烦 网盘&#xff1a;https://pan.baidu.com/s/13FCKXE8t_AZeixcR_bEhXg 提取密码&#xff1a;123…

从Linux内核探索 Socket 的本质

目录 一、引言 二、Socket 的概念 三、Socket 的使用场景 四、Socket 的设计 五、提供 Socket 层 六、Socket 如何实现网络通信 &#xff08;一&#xff09;建立连接 &#xff08;二&#xff09;数据传输 七、Socket 怎么实现“继承” 八、总结 一、引言 相信大家刚…

[Zer0pts2020]Can you guess it?1

打开题目 看到信息随便输入一个数&#xff0c;显示错误 查看源代码 看到php代码&#xff0c;代码审计 <?php include config.php; // FLAG is defined in config.php if (preg_match(/config\.php\/*$/i, $_SERVER[PHP_SELF])) { exit("I dont know what you are t…

以node / link文件表征的道路网络-----dijkstra算法yyds-----基于南京公路公开数据做路径规划(上)

前文已经基于公开数据&#xff0c;获得了南京的全域高速公路的路网数据&#xff0c;这些以node / link文件表征的道路网络不仅延续了osm地图中所包含的经纬度、名称、容量等信息 &#xff0c;还包含了一个重要的道路等级字段 “link_type_name”。 交通部门一般以高速公路、国…

ThinkPHP的SQL注入漏洞学习

目录 漏洞环境 漏洞概要 函数学习 call_user_func函数 mplode函数 漏洞分析 漏洞修复 攻击总结 漏洞环境 漏洞存在于 Builder 类的 parseData 方法中。由于程序没有对数据进行很好的过滤&#xff0c;将数据拼接进 SQL 语句&#xff0c;导致 SQL注入漏洞 的产生。 漏洞…