【C++】:AVL树

news2024/11/28 4:43:22

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关多态的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. AVL树的概念

2. AVL树节点的定义

3. AVL树的插入

3.1 AVL树的旋转

1. 右单旋

2. 左单旋

3. 先左单旋再右单旋(双旋)

4. 先右单旋再左单旋(双旋)

4. AVL树的验证 

4.1 AVL树的性能 


1. AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在
O(\log N),搜索时间复杂度O(\log N)

2. AVL树节点的定义

在这里我们定义AVL树使用一个pair来存储数据,关于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;             //存储节点数据的kv模型
	int _bf;                    //平衡因子

	//
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

3. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  • 1. 按照二叉搜索树的方式插入新节点
  • 2. 调整节点的平衡因子

如果插入的节点在左边,那么就需要将平衡因子--,如果插入的节点在右边,平衡因子就需要++,并且需要注意的是当平衡因子改变之后,如果为0,代表平衡,不需要其他操作,如果改变之后为1或者-1,那么同样的也需要对它祖先的平衡因子进行改变,直到它的父节点为空即可停止,如果改变之后的平衡因子为-2或者2,那么就表示出现的不平衡现象,需要进行旋转。

//插入
bool Insert(const pair<K,V>& kv)
	{
		//1. 先按照二叉搜索树的规则将节点插入到AVL树中
		Node* cur = _root;
		Node* parent = nullptr;
		if (_root == nullptr)
		{
			_root = new Node(kv);
			retrun true;
		}
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->rigth;
			}
			else
				retrun false;
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//2. 调整节点的平衡因子
		while (parent)
		{
			if (parent->_left == cur)    //插入的节点在左边时,平衡因子--
				parent->_bf--;
			else (parent->_right == cur) //插入的节点在右边时,平衡因子++
				parent->_bf++;

			if (parent->_bf == 0)        //判断平衡因子时候合理
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)  //插入新的节点导致高度变化,
															 //所以得依次向上去调整它们父亲的平衡因子
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//平衡出现差错,需要进行旋转调整
				//...
			}
			else   //如果平衡因子不为上述情况,那么就不能再继续了
				assert(false);
		}
		return true;
	}

3.1 AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:


1. 右单旋

新节点插入较高左子树的左侧---左左:右单旋

需要注意的一个点,我们不确定这棵树是不是另外一棵树的一个子树,所以还需要将parent的父亲记录下来,如果这棵树就是独立的,那么只需要将subL设置为新的根节点即可,如果是另外一棵树的子树,那么就需要将旋转完之后的树链接在它的祖先上。

首先将根节点记为parent,因为需要右旋,所以肯定是左边高往右边旋转,所以将parent的左子树记为subL,将subL的右子树记为subLR,接下来就需要需要旋转了,将parent的左指向subLR,然后将subL的右指向parent,这样子就完成了右旋。

需要注意的是在修改完各各节点的链接时,它们原来的父亲关系就需要重新设置,比如上面的图,将parent的父亲指向subL,将subLR的父亲指向parent(subLR不一定为空,所以需要判断一下再进行链接)此时只需要将各各节点的平衡因子修改即可,在右旋之后可以发现subL和parent的平衡因子都变成了0,所以直接对它们各自的平衡因子修改即可

//右单旋
	void Rotate_right(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* ppNode = parent->_parent;
		//旋转链接
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;
		
		subL->_right = parent;
		parent->_parent = subL;
		
		//链接祖先
		if (parent == _root)
		{
			_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;
	}
2. 左单旋

左单旋和右单旋的情况类似,只不过左单旋是右边高往左边旋转,类似的可以参考右单旋的思路。

//左单旋
	void Rotate_left(Node* parent)
	{
		Node* subR = parent->_right;  //右子树的节点
		Node* subRL = subR->_left;    //
		Node* ppNode = parent->_parent;
		//旋转->重新链接
		subR->_left = parent;
		parent->_parent = subR;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		//链接祖先
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
		//修改平衡因子
		parent->_bf = subR->_bf = 0;
	}
3. 先左单旋再右单旋(双旋)

左右双旋在这里依旧存在三种情况:

①插入在subLR的左边

②插入在subLR的右边

③subLR就是新插入的节点

双旋的情况可以看到是一个折线的样子,根据偏转的方向来确定首先向哪边旋转,先将根节

点记为parent,再将parent的左记为subL,将subL的右记为subLR,先以subL为根进行左单旋,然后再以parent为根进行右单旋。然后根据上述三种情况修改平衡因子。

可以根据subLR的平衡因子来修改parent、subL、subLR的平衡因子:

①如果subLR的平衡因子是-1,那么在双旋完之后,需要将parent的平衡因子修改为1,将其他两个修改为0。

②如果subLR的平衡因子是1,那么在双旋完之后,需要将subL的平衡因子修改为-1,将其他两个修改为0。

③如果subLR的平衡因子是0,那么parent、subL、subLR的平衡因子修改为0。

//左右双旋
	void Rotate_left_right(Node* parent)
	{
		Node* subL = parent->-left;
		Node* subLR = subL->_right;
		//记录插入之后的平衡因子
		int bf = subLR->_bf;

		//先左旋
		Rotate_left(subL);
		//再右旋
		Rotate_right(parent);

		//修改平衡因子
		if (bf == 0)   //本身就是新插入的节点
		{
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else if (bf == -1)  //左边插入
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 1)  //右边插入
		{
			subL->_bf = -1;
			parent->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(flase);
		}
	}
4. 先右单旋再左单旋(双旋)

同样的这里也存在三种情况:

①插入在subRL的左边

②插入在subRL的右边

③subRL就是新插入的节点

右左双旋的逻辑和左右双旋的逻辑一样,可以参考上面的。

//右左双旋
	void Rotate_right_left(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		Rotate_right(subR);
		Rotate_left(parent);
		if (bf == 0)
		{
			subR->_bf = subRL->_bf = parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
			assert(false);
	}

解决完插入中的旋转问题之后我们将旋转融入到插入的整个代码中:

bool Insert(const pair<K,V>& kv)
	{
		//1. 先按照二叉搜索树的规则将节点插入到AVL树中
		Node* cur = _root;
		Node* parent = nullptr;
		if (_root == nullptr)
		{
			_root = new Node(kv);
			retrun true;
		}
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->rigth;
			}
			else
				retrun false;
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//2. 调整节点的平衡因子
		while (parent)
		{
			if (parent->_left == cur)    //插入的节点在左边时,平衡因子--
				parent->_bf--;
			else (parent->_right == cur) //插入的节点在右边时,平衡因子++
				parent->_bf++;

			if (parent->_bf == 0)        //判断平衡因子时候合理
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)  //插入新的节点导致高度变化,
															 //所以得依次向上去调整它们父亲的平衡因子
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//平衡出现差错,需要进行旋转调整
				if (parent->_bf == 2 && cur->_bf == 1)         //左单旋情况
					Rotate_left(parent);
				else if (parent->_bf == -2 && cur->_bf == -1)  //右单旋情况
					Rotate_right(parent);
				else if (parent->_bf == -2 && cur->_bf == 1)   //左右双旋情况
					Rotate_left_right(parent);
				else if (parent->_bf == 2 && cur->_bf == -1)   //右左双旋情况
					Rotate_right_left(parent);
				// 1、旋转让这颗子树平衡了
				// 2、旋转降低了这颗子树的高度,恢复到跟插入前一样的高度,所以对上一层没有影响,不用继续更新
				break;
			}
			else   //如果平衡因子不为上述情况,那么就不能再继续了
				assert(false);
		}
		return true;
	}

4. AVL树的验证 

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  • 1. 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

  • 2. 验证其为平衡树

每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
节点的平衡因子是否计算正确
 

//判断是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
	//计算树的高度
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHeight = _Height(root->_left);   //左子树的高度
		int rightHeight = _Height(root->_right); //右子树的高度
		//返回左右子树高度较高的那一颗树+1
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftHeight = _Height(root->_left);   //左子树的高度
		int rightHeight = _Height(root->_right); //右子树的高度
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
		//右子树-左子树高度不超过2则为AVL树
		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

4.1 AVL树的性能 

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即O(\log N)但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!  

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

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

相关文章

C语言 内联函数 + 递归函数

函数分类 内联函数 1&#xff09;内联函数在编译时将函数的代码直接插入到调用它的地方&#xff0c;而不是通过函数调用的方式执行&#xff0c;从而减少了函数调用的开销&#xff0c;提高了代码的执行速度 2&#xff09;使用 inline 关键字来声明 3&#xff09;将函数声明为内联…

深入理解Dubbo-4.Dubbo扩展SPI

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

Redis探秘:AOF日志与数据持久性之旅

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天来聊聊Redis。你知道吗&#xff0c;Redis作为一个超高效的内存数据库&#xff0c;真的是超级给力。它可以秒速处理数据&#xff0c;让咱们的应用运行得飞快。但是&#xff0c;小黑得告诉你&#xff0c;虽…

四. 基于环视Camera的BEV感知算法-BEVFormer

目标 前言0. 简述1. 算法动机&开创性思路2. 主体结构3. 损失函数4. 性能对比5. BEVFormerv2总结下载链接参考 前言 自动驾驶之心推出的《国内首个BVE感知全栈系列学习教程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习下课程第四章—…

nginx多ip部署

1.修改网卡信息自定义多个IP 进入/etc/sysconfig/network-scripts&#xff0c;编辑ifcfg-ens33网卡文件。将dhcp动态分配修改成static&#xff0c;同时添加ip地址子网掩码、网关和DNS。 修改完成后重启网卡&#xff0c;systemctl restart network 2.修改nginx配置文件 有几个…

【数据结构实践课设】新生报道注册管理信息系统

目录 1.主要框架 2.写入文件 3.读取文件 4.注册学生信息 5.增加学生信息 6.删除学生信息 7.按姓名查询 8.按班级查询 9.按专业查询 10.打印学生信息 11.完整代码 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所…

【S32K3环境搭建】-0.4-使用SEGGER J-Link烧录调试程序

【S32K3_MCAL从入门到精通】合集&#xff1a; S32K3_MCAL从入门到精通https://blog.csdn.net/qfmzhu/category_12519033.html 导入一个编译没有报错的S32K312工程。接着在菜单栏中&#xff0c;依次选择Debug下拉箭头 -- > Debug Configuration&#xff1b; 在弹出的Create…

FL Studio 21注册机激活码序列号下载 附激活码

FL Studio 21 keygen激活码序列号是一款基于同名软件激活程序。操作非常简单。用户只需使用本激活工具生成激活码&#xff0c;即可一键激活软件所有功能&#xff0c;无需付费。去购买注册程序&#xff0c;永久免费使用所有功能。很多用户应该都知道&#xff0c;专业的编辑工具只…

SpringCloud-高级篇(六)

&#xff08;1&#xff09;Seata架构 &#xff08;2&#xff09;部署TC服务 修改注册中心&#xff1a;做服务注册&#xff0c;tc服务配置注册中心&#xff0c;tc服务可以做集群&#xff0c;他也是一个服务 &#xff0c;微服务里面RM 跟TM跟它建立联系肯定是基于注册中心去找&a…

using dapers on diffusers: Dreambooth, Texual Inversion, LoRA and IP-Adapter

using dapers on diffusers: Dreambooth, Texual Inversion, LoRA and IP-Adapter 参考自&#xff1a;https://huggingface.co/docs/diffusers/using-diffusers/loading_adapters 如今&#xff0c;对于 diffusion 模型&#xff0c;有许多高效的训练技术来微调一个定制化的模型&…

gittee使用教学

一、git简介 Git是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效的处理任何大小项目的版本管理。 核心功能&#xff1a; 项目的版本管理 团队协同开发 二、准备工作 1、下载 Git 2、除了选择安装位置以外&#xff0c;其他都无脑安装 3、检查一下安装情况 win…

Android系统中使用Cunit测试C/C++接口

Android系统中使用Cunit测试C/C接口 Cunit是C/C语言的单元测试框架&#xff0c;但常用于Windows和Linux开发中。 Android系统中经常有jni、so库、hal service等都是C/C实现&#xff0c;本文讲解如何将Cunit嵌入Android中&#xff0c;用于测试一些C/C api。 Cunit简介 Cunit是很…

Post Quantum Fuzzy Stealth Signatures and Applications

目录 笔记后续的研究方向摘要引言贡献模块化框架模糊构造实施适用于FIDO Post Quantum Fuzzy Stealth Signatures and Applications CCS 2023 笔记 后续的研究方向 摘要 自比特币问世以来&#xff0c;基于区块链的加密货币中的私人支付一直是学术和工业研究的主题。隐形地址…

Java 何时会触发一个类的初始化

Java 何时会触发一个类的初始化&#xff1f; 使用new关键字创建对象访问类的静态成员变量 或 对类的静态成员变量进行赋值调用类的静态方法反射调用类时&#xff0c;如 Class.forName()初始化子类时&#xff0c;会先初始化其父类&#xff08;如果父类还没有进行过初始化的话&a…

动态规划_最小花费爬楼

//给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 // // 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 // // 请你计算并返回达到楼梯顶部的最低花费。 …

小模型学习(1)-人脸识别

【写作背景】因为最近一直在研究大模型&#xff0c;在与客户进行交流时&#xff0c;如果要将大模型的变革性能力讲清楚&#xff0c;就一定要能将AI小模型的一些原理和效果讲清楚&#xff0c;进而形成对比。当然这不是一件简单的事情&#xff0c;一方面大模型分析问题的的本质原…

C# 使用CancellationTokenSource 取消Task执行

写在前面 在Task创建并执行后&#xff0c;如果状态发生了变化&#xff0c;需要取消正在执行中的Task&#xff0c;除了使用主线程上的共享变量来判断之外&#xff0c;更优雅的方式就是就是用CancellationTokenSource来取消任务的执行。 代码实现 public static void CancelTas…

【AIGC】Midjourney高级进阶版

Midjourney 真是越玩越上头&#xff0c;真是给它的想象力跪了~ 研究了官方API&#xff0c;出一个进阶版教程 命令 旨在介绍Midjourney在Discord频道中的文本框中支持的指令。 1&#xff09;shorten 简化Prompt 该指令可以将输入的Prompt为模型可以理解的语言。模型理解语言…

(2022|ICLR,kNN检索,扩散,仅图像训练)KNN-Diffusion:通过大规模检索生成图像

KNN-Diffusion: Image Generation via Large-Scale Retrieval 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 相关工作 3. 方法 3.1 仅文本图像处理 4. 实验 …

PyQt下使用OpenCV实现人脸检测与识别

背景&#xff1a; 一 数字图像处理与识别警务应用模型 基于前期所学知识&#xff0c;与公安实践相结合&#xff0c;综合设计数字图像处理与识别警务应用模型,从下列4个研究课题中选择2个进行实验实现&#xff1a;图像增强与复原、人脸检测与识别、虹膜内外圆检测与分割、车牌…