数据结构【AVL树模拟实现】

news2024/11/15 19:27:11

目录

AVL树概念

AVL树结构

insert

AVL树的旋转

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

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

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

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

insert实现

测试二叉树是否为AVL树


AVL树概念

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

一棵AVL树性质:

1.它的左右子树都是AVL(任意一个子树左右高度差都不超过1)

2.左右子树高度之差(简称平衡因子)的绝对值不超过1(平衡因子是其中一种实现方式,判断平衡因子即可判断是否为AVL树)

3.平衡因子 = 右子树高度 - 左子树高度

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

AVL树结构

引入平衡因子,需要三岔链,原因在于新插入节点还需保持AVL树的特性,新插入节点后平衡因子需要改变(更新新插入节点祖先路径)

	template<class K,class V>
	struct AVLTreeNode
	{
		AVLTreeNode<K, V>* _left;
		AVLTreeNode<K, V>* _right;
		AVLTreeNode<K, V>* _parent;
		int _bf;//平衡因子
		pair<K, V> _kv;
	};

	template<class K,class V>
	class AVLTree
	{
	public:
		typedef AVLTreeNode<K, V> Node;
	private:
		Node* _root;
	};

insert

插入后AVL树有可能导致不平衡,此时需要沿着parent更新平衡因子

更新平衡因子规则:

1.如果此时是在parent的右,parent平衡因子++

2.如果此时是在parent的左,parent平衡因子--

3.更新后parent平衡因子如果为0,说明之前是1--/-1++,代表左右子树一边高一边低。插入后变为0,表示两边子树一样高。此时parent则不用继续往上更新(左右子树平衡)

4.更新后parent平衡因子如果为1/-1,说明原来parent平衡因子只可能是0,0代表左右子树高度相等。更新完后为1/-1,代表左右子树一边高一边低,表示parent的高度也改变了,继续往上更新平衡因子(子树高度改变影响父亲)

5.更新后praent平衡因子如果为2/-2,此时parent所在的子树需要旋转处理

insert的基本逻辑,不包括旋转,旋转下面总结

		bool insert(const pair<K,V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			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
				{
					return false;
				}
			}
			cur = new Node(kv);
			if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			else if (parent->_kv.first < kv.first)
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//while控制平衡因子,最坏的情况下,更新到根节点
			while (parent)
			{
				if (parent->_left == cur)
				{
					parent->_bf--;
				}
				else if (parent->_right == cur)
				{
					parent->_bf++;
				}

				if (parent->_bf == 1 || parent->_bf == -1)
				{
					parent = parent->_parent;
					cur = cur->_parent;//沿着三岔链往上走
				}
				else if (parent->_bf == 0)
				{
					break;
				}
				else if(parent->_bf == 2 || parent->_bf == -2)//parent所在子树不平衡,开始旋转
				{

				}
				else
				{
					assert(false);//直接报错,插入之前平衡因子已经出问题
				}
			}
			return true;
		}

AVL树的旋转

旋转规则:

1.如果有不平衡的情况,一定在parent(parent和cur是沿着三岔链更新上去的)

2.旋转成平衡二叉搜索树树

3.旋转后还需要继续保持平衡二叉搜索树的特性

4.具象图有无限种情况,此时我们需要抽象总结归纳avl树旋转的场景和情况

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

抽象图:h可以是任意高度(h>=0)

此处抽象图要求c本身需要平衡,否则插入可能导致c自身旋转

新节点插入较高右子树的右侧c中(无论h有多高,  新增的节点在c的左or右),插入在c中结果都是parent引发的旋转(parent->bf == 2)

旋转方式:让b作为parent的右子树(b中所有节点都比parent大,没有违反规则);让parent这颗树(a+b)作为tmp处的左子树(所有节点都小于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡

此时旋转就有三个位置需要标记:parent位置,parent->right称为subR,parent->right->left称为subRL

此时有以下情况需要处理:

1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent

2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点

3.需要改变六个指针的位置

4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可

5.只有parent和subR的平衡因子受到影响(a,b不变),旋转完后parent和subR平衡因子变为0

	void RotaL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			Node* pparent = parent->_parent;
			parent->_right = subRL;//1
			if (subRL != nullptr)
			{
				subRL->_parent = parent;//2
			} 
			subR->_left = parent;//3
			subR->_parent = pparent;//4
			parent->_parent = subR;//5
			if (pparent)
			{
				if (pparent->_left == parent)
				{
					pparent->_left = subR;//6
				}
				else
				{
					pparent->_right = subR;//6
				}
			}
			else//root == nullptr,此时subR为根
			{
				_root = subR;//6
			}
		}

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

新节点插入较高左子树的左侧a中(无论h有多高,  新增的节点在a的左or右),插入在a中结果都是parent引发的旋转(parent->bf == -2) 

旋转方式:让b作为parent的左子树(b中所有节点都比parent小,没有违反规则);让parent这棵树 (b+c)作为tmp处的右子树(所有节点都大于tmp,没有违反规则),此时左子树是h+1,右子树是h+1,整棵树保持高度平衡

此时旋转就有三个位置需要修改标记:parent位置,parent->left称为subL,parent->left->right称为subLR

此时有以下情况需要处理:

1.注意parent旋转后,有可能是根节点,有可能是整棵树中的一个子树,此时还需要保存parent->_parent,来让旋转后的subR指向_parent

2.如果parent->_parent为nullptr,则代表旋转后subR变成根节点

3.需要改变六个指针的位置

4.一次插入最多更新高度次,在高度次中,只会出现一次旋转更新。如果在while更新平衡因子过程中出现旋转,旋转完成后整棵树即可保持平衡,插入之前树的高度是h+2,插入时高度变成h+3,旋转完成后高度继续变为h+2,对上一层没有影响,直接break跳出旋转即可

5.只有parent和subR的平衡因子受到影响(b,c不变),旋转完后parent和subR平衡因子变为0

		void RotaR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			Node* pparent = parent->_parent;
			parent->_left = subLR;//1
			if (subLR)
				subLR->_parent = parent;//2
			subL->_right = parent;//3
			parent->_parent = subL;//4
			subL->_parent = pparent;//5
			if (pparent)
			{
				if (pparent->_left == parent)
				{
					pparent->_left = subL;//6
				}
				else
				{
					pparent->_right = subL;//6
				}
			}
			else
			{
				_root = subL;//6
			}
			parent->_bf = subL->_bf = 0;
		}

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

如果此时新插入的节点在新节点插入较高左子树的右侧,不能使用右单旋

只使用右单旋解决:无法解决问题(从左子树平衡因子2变成右子树平衡因子2,旋转呈对称)

旋转思路:先对30进行左单旋,再对60进行右单旋

旋转的情况是相同的,只是平衡因子会变化

旋转的过程不变,但是平衡因子的更新却不同,原因在于可能插入在b中,也可能插入在c中

		void RotaLR(Node* parent)
		{
			RotaL(parent->_left);
			RotaR(parent);//parent每次都在-2处
			//处理平衡因子,有三种情况
		}

在c中插入时,90平衡因子变为0,60变为0,30变为-1

还有一种极端情况,当h为0时,三个平衡因子都为0(60就是新增的节点)

 

插入在左子树的右侧不同位置,三个位置平衡因子不同

看最开始插入数据,60的平衡因子会根据插入的不同位置,平衡因子也右不同的变化

当60(subLR)平衡因子为-1时,插入在b中;平衡因子为1时,插入在c中;为0时,h高度为0

 

插入不同位置,平衡因子变化也不同,平衡因子最后变化看插入在哪里(也就是插入时subLR即可),h==0时处平衡因子即为0,直接推导出不画图

		void RotaLR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;
			RotaL(parent->_left);
			RotaR(parent);//parent每次都在-2处
			//处理平衡因子,有三种情况
			if (bf == 1)//新增在
			{
				parent->_bf = 0;
				subL->_bf = -1;
			}
			else if (bf == -1)//新增在
			{
				parent->_bf = 1;
				subL->_bf = 0;
			}
			else if (bf == 0)//h == 0
			{
				parent->_bf = 0;
				subL->_bf = 0;
			}
			else
			{
				assert(false);
			}
			subLR->_bf = 0;
		}

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

看60的bf确定平衡因子是左/右种的哪一种情况,h==0时平衡因子直接为0不用画了

 

随机一种右左双旋的情况,旋转情况一样,只是平衡因子不同,看下图配合

 

根左右双旋同理,分三种情况的平衡

		void RotaRL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subL->_left;
			int bf = subRL->_bf;
			RotaL(parent->_right);
			RotaR(parent);//parent每次都在-2处
			//处理平衡因子,有三种情况
			if (bf == 1)//新增在c
			{
				parent->_bf = -1;
				subR->_bf = 0;
			}
			else if (bf == -1)//新增在b
			{
				parent->_bf = 0;
				subR->_bf = 1;
			}
			else if (bf == 0)//h == 0
			{
				parent->_bf = 0;
				subR->_bf = 0;
			}
			else
			{
				assert(false);
			}
			subRL->_bf = 0;
		}

 

 

insert实现

		bool insert(const pair<K,V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			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
				{
					return false;
				}
			}
			cur = new Node(kv);
			if (parent->_kv.first > kv.first)
			{
				parent->_left = cur;
			}
			else if (parent->_kv.first < kv.first)
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//while控制平衡因子,最坏的情况下,更新到根节点
			while (parent)
			{
				if (parent->_left == cur)
				{
					parent->_bf--;
				}
				else if (parent->_right == cur)
				{
					parent->_bf++;
				}

				if (parent->_bf == 1 || parent->_bf == -1)
				{
					parent = parent->_parent;
					cur = cur->_parent;//沿着三岔链往上走
				}
				else if (parent->_bf == 0)
				{
					break;
				}
				else if(parent->_bf == 2 || parent->_bf == -2)//开始旋转
				{
					//左单旋
					if (parent->_bf == 2 && cur->_bf == 1)
					{
						RotaL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == -1)
					{
						RotaR(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)//左右单旋
					{
						RotaLR(parent);
					}
					else if (parent->_bf == 2 && cur->_bf == -1)
					{
						RotaRL(parent);
					}
					else
					{
						assert(false);//直接报错,平衡因子已经出问题
					}
					break;
				}
				else
				{
					assert(false);//直接报错,插入之前平衡因子已经出问题
				}
			}
			return true;
		}

测试二叉树是否为AVL树

中序遍历有序只能代表是搜索树;

通过高度和节点的平衡因子是否计算正确来判断是否为AVL树,如果不是AVL树,大量随机插入后某个节点左右子树高度差>=2,高度差即可和当前节点平衡因子比较。即可判断出是否为AVL树,同时注意需要递归,左右子树也需要保持AVL树。

        bool IsBalance()//判断AVL树
		{
			return _IsBalance(_root);
		}
private://需要私有中写子函数,因为需要递归
		bool _IsBalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int left = height(root->_left);
			int right = height(root->_right);
			int diff = right - left;
			if (abs(diff) >= 2)
				return false;

			// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
			// pRoot平衡因子的绝对值超过1,则一定不是AVL树
			if (diff != root->_bf || (diff > 1 || diff < -1))
				return false;

			return abs(diff) < 2 
            && _IsBalance(root->_left) 
            && _IsBalance(root->_right);//判断AVL树,也要递归判断所有子树是否为AVL树

		}

		int height(Node* root)
		{
			if (root == nullptr)
				return 0;

			int k1 = height(root->_left);
			int k2 = height(root->_right);

			return max(k1,k2)+1;
		}

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

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

相关文章

Bug解决:出现C++:internal compiler error: killed(program cc1plus)

最近在学习hyperscan过程中&#xff0c;安装的过程总是会出现 C&#xff1a;internal compiler error: killed(program cc1plus) 反复查找之后既不是版本问题也不是依赖问题&#xff0c;查阅了很多解决方案后&#xff0c;确认是交换空间不足&#xff0c;所以我们解决的方法是临…

Kotlin 开发Android app(七)上:Kotlin函数fun

对于函数来说&#xff0c;现在的语言越来越想把它往变量上靠。 确实对于函数来说&#xff0c;他应该有很多变量的特性。 在Kotlin 中&#xff0c;定义函数是很简单的一件事情&#xff0c;我觉得编程有的时候是被一些语言给高复杂了&#xff0c;命名很简单的一些事情&#xff0…

matlab绘图

clear a-2:0.1:2; b-3:0.1:3; [x,y]meshgrid(a,b); z(1-(x.2)/4-(y.2)/9).^(1/2); mesh(x,y,z) hold on mesh(x,y,-z) clear a-1:0.1:1; b-2:0.1:2; [x,y]meshgrid(a,b); z(4/9)*(x.2)(y.2); mesh(x,y,z)

运维监控系统 PIGOSS BSM 拓扑自动发现原理

PIGOSS BSM提供了自动发现拓扑功能&#xff0c;能够发现全网拓扑、指定网段拓扑、路由拓扑&#xff0c;能够自动关联系统已经监控的设备的状态在拓扑图上实时显示。在做全网发现的时候&#xff0c;可以指定网段或起始路由器&#xff0c;并设定全网的snmp配置参数后&#xff0c;…

vue 使用webpack打包,出现路径404 ,导致白屏webpack-bundle-analyzer使用

使用webpack打包时&#xff0c;遇到两个问题&#xff0c;导致页面出现白屏 一个是cdn对于静态文件限制大小&#xff0c;不能大于2MB&#xff0c;超过这个大小后&#xff0c;就不能上传cdn成功&#xff0c;导致页面加载时&#xff0c;长时间白屏&#xff0c;且找不到资源&#x…

INTERSPEECH 2022|FS-CANet: 基于全带子带交叉注意力机制的语音增强

INTERSPEECH 2022 FS-CANet: 基于全带子带交叉注意力机制的语音增强 本文由清华大学与腾讯天籁实验室、香港中文大学合作&#xff0c;提出了一个全带-子带交叉注意力&#xff08;FSCA&#xff09;模块来交互融合全局信息和局部信息&#xff0c;并将其应用于FullSubNet&#…

Dubbo源码(十) 与Spring一起学习Dubbo里的Aware

目录 一、Spring 1.BeanNameAware 2. BeanClassLoaderAware 3. ApplicationContextAware 4. EnvironmentAware 5. ApplicationEventPublisherAware 6. aware注入时机 二、Dubbo 1. ExtensionAccessorAware 三、小结 现在很多同行做java开发几年了&#xff0c;被迫停留…

[附源码]java毕业设计竞价拍卖系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

灵雀云ACP 斩获“2022金边奖-最佳云原生边缘云平台”

近日&#xff0c;由边缘计算社区主办的全球边缘计算大会上海站成功召开&#xff0c;灵雀云凭借出色的全栈云原生技术实力、专业的高品质服务以及在边缘云场景的丰富落地实践&#xff0c;斩获“2022金边奖-最佳云原生边缘云平台”奖项。 “十四五”规划中明确指出要“协同发展云…

使用kubeadm部署kubernetes集群

文章目录环境环境初始化配置hosts配置时钟同步禁用firewalld、selinux、postfix禁用swap分区开启IP转发、修改内核信息配置IPVS安装Docker配置yum源安装docker-ce配置镜像加速器安装kubernetes组件配置yum源安装kubeadm、kubelet、kubectl工具配置containerd部署master安装pod网…

UTF-8、Unicode编码与汉字的相关内容

介绍 UTF-8是Unicode的一种实现方式&#xff0c;比如一个汉字用Unicode编码表示是两个字节&#xff0c;而用UTF8编码表示则为3个字节。 之所以写这篇文章&#xff0c;是因为我的webserver程序中&#xff0c;浏览器发送资源请求且该资源名为中文时出现了编码问题。 UTF8编码 U…

【SQL 中级语法 2】自连接的用法

SQL的连接运算根据其特征的不同&#xff0c;有着不同的名称&#xff0c;如内连接、外连接、交叉连接等。一般来说&#xff0c;这些连接大都是以不同的表或视图为对象进行的&#xff0c;但针对相同的表或相同的视图的连接也并没有被禁止。针对相同的表进行的连接被称为“自连接”…

python之排序

目录1. 对一维array中的数值进行从大到小排序2. 将DataFrame的列逆序排列3. 根据字符串中的数字进行排序3.2 啊4. 列表参考资料1. 对一维array中的数值进行从大到小排序 import numpy as np a np.array([5,6,8,2,1,7,5,3,90,78,62,5,4,2,9,4]) # b a.sort(axis0,kindquickso…

抑制剂拮抗剂等小分子化合物

小分子化合物主要通过调节其蛋白靶点的活性发挥作用。目前小分子化合物的蛋白靶点主要包括酶、离子通道和受体三大类。根据靶点种类的不同&#xff0c;小分子化合物发挥着不同的作用。 1. 酶的抑制剂&#xff08;enzyme inhibitor&#xff09; 在所有的小分子化合物中&#xff…

【虹科案例】虹科脉冲发生器在半导体行业中的应用

非易失性存储单元特点 存储器研究的趋势是开发一种称为非易失性 RAM 的新型存储器&#xff0c;它将 RAM 的速度与大容量存储器的数据存储相结合。几年来有许多新单元类型的提议&#xff0c;例如 FeRAM&#xff08;铁电存储器&#xff09;、ReRAM&#xff08;电阻式存储器&…

C++string类的模拟实现以及经验分享

文章目录1. 为什么学习string类&#xff1f;1.1 C语言中的字符串1.2 两个面试题2. string类的实现构造函数&#xff1a;拷贝构造函数赋值运算符重载&#xff1a;析构函数流提取运算符重载1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 C语言中&#xff0c;字符串是…

5款十分小众,却又非常好用的良心软件

今天推荐5款十分小众的软件&#xff0c;知道的人不多&#xff0c;但是每个都是非常非常好用的&#xff0c;有兴趣的小伙伴可以自行搜索下载。 1.杀毒软件——火绒安全软件 首先说一下国产杀软之光&#xff0c;这是一款电脑安全软件&#xff0c;病毒库更新及时&#xff0c;界面…

【文档+视频】Verdi基础教程

目录 前言 1.Verdi 环境配置 2.VCS 产生Verdi 波形 1、tb中加入相应的系统函数 2、makefile中加入相应的选项 3.nTrace 1、如何调用Verdi&#xff1f; 【重点】 2、如何查看包含的设计架构&#xff1f; 3、如何查寻模块实例化的位置&#xff1f;【重点】 4、在nTrace…

Linux网络编程11——简单的web服务器

学习视频链接 02-web大练习的概述_bilibili_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA/?p132&spm_id_frompageDriver&vd_source0471cde1c644648fafd07b54e303c905 目录 一、项目展示 二、HTTP 协议基础 2.1 HTTP协议基础。 2.2 请求消息(R…

个人博客系统

目录一、项目简介二、项目开发流程2.1 准备工作2.2 实现Vue层2.2 实现Model层2.4 实现Controller层一、项目简介 基于servlet&#xff0c;采用前后端分离的方式&#xff0c;实现个人博客系统&#xff0c;功能包括&#xff1a;登录、注销、编辑、删除博客、发布博客等. 二、项…