【C++】AVL树(旋转、平衡因子)

news2024/9/23 23:31:46

🌈个人主页:秦jh_-CSDN博客
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12575764.html?spm=1001.2014.3001.5482

 9efbcbc3d25747719da38c01b3fa9b4f.gif​ 

目录

前言

AVL树的概念

 节点

插入

AVL树的旋转 

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

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

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

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

AVL树的验证 

 AVL树的性能

完整代码


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了AVL树的相关内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

AVL树的概念

 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。

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

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

如果一棵二叉搜索树是高度平衡的,它就是AVL树。

插入的总体原则:

  1.  按照搜索树规则插入
  2. 更新插入节点的祖先节点的平衡因子。
    1. 如果插入在父亲左边,父亲的平衡因子--。
    2. 如果插入在父亲右边,父亲的平衡因子++。
    3. 父亲平衡因子==0,则父亲所在子树高度不变,不再继续往上更新,插入结束。
    4. 父亲平衡因子==1or-1,父亲所在子树高度变了,继续往上更新。
    5. 父亲平衡因子==2or-2,父亲所在子树已经不平衡了,需要旋转处理。

 节点

插入

	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->_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;

		//更新平衡因子
		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 = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//当前子树出问题了,需要旋转平衡一下

				break;
			}
			else
			{
				//理论而言不可能出现该情况
				assert(false);
			}
		}


		return true;
	}

上面是插入的大体流程,旋转操作还未给出。

AVL树的旋转 

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

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

这里以抽象图进行分析,因为具体的情况有很多种,无法画出。

注意:a子树的情况必须是插入后会引发祖先节点的更新,而不是只是内部变化。如下图情况就不符合要求。

旋转流程:新节点插入在a树中,导致以60为根的二叉树不平衡。所以就要右单旋。

右单旋:把60的左子树高度减少,即把60取出来,让30的右子树变成60的左子树,再把以60为根的树变成30的右子树。30成为新的根。

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

		parent->_left = subLR;
		if (subLR) //节点可能为空
			subLR->_parent = parent;

		subL->_right = parent; //旧父节点变成subL的右节点
		
		Node* ppNode = parent->_parent;  //该不平衡节点可能不是根节点,所以要找到它的父节点
		parent->_parent = subL;				

		if (parent == _root)   //如果该节点是根节点
		{
			_root = subL;		
			_root->_parent = nullptr;
		}
		else  //不平衡节点只是一棵子树
		{
			if (ppNode->_left == parent)  //如果旧父节点等于爷爷节点的左节点,新父节点为爷爷节点的左节点
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;	//新父节点指向爷爷节点。
		}

		parent->_bf = subL->_bf = 0;  //只需要修改这两个的平衡因子


	}

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

参考右单旋。

左单旋和右单旋的调用如下图:

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

单旋用在一边一直高的情况。双旋是先一边高再另一边高的情况。

双旋的的原理就是把折线变成直线,再像处理直线一样旋转。

双旋可以复用单旋,但双旋主要要搞清平衡因子的变化。

第一种情况: 

双旋的结果:60的左边给了30的右边,60的右边给了90的左边,30和90分别成为60的左右,60成为根。

上图是插入b引起的旋转,当插入c时是第二种情况,如下图:

上面两种插入位置的不同,导致最终的平衡因子不同。

第三种情况:

h==0时,60就是新增节点,最终的平衡因子也不同。

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right; 

		int bf = subLR->_bf;  //记录未旋转前subLR的平衡因子

		RotateL(parent->_left);  
		RotateR(parent);
	
		if (bf == -1)  //如果bf为-1,即插入在subLR的左边
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1) //插入在subLR的右边
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_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);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

AVL树的验证 

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

  1. 验证其为二叉搜索树。如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树。每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确 

 因为root是私有的,又因为需要递归检查每棵子树是否平衡,所以可以写一个私有的_IsBalance方法,通过公有的IsBalance方法来调用。

 AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这 样可以保证查询时高效的时间复杂度,即O(logN)。 但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时, 有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑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 pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	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->_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;

		//更新平衡因子
		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 = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//当前子树出问题了,需要旋转平衡一下
				if (parent->_bf == -2 && cur->_bf == -1) //左边高,右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)//右边高,左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{ 
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent); 
				}

				break;
			}
			else
			{
				//理论而言不可能出现该情况
				assert(false);
			}
		}


		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

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

		parent->_left = subLR;
		if (subLR) //节点可能为空
			subLR->_parent = parent;

		subL->_right = parent; //旧父节点变成subL的右节点
		
		Node* ppNode = parent->_parent;  //该不平衡节点可能不是根节点,所以要找到它的父节点
		parent->_parent = subL;				

		if (parent == _root)   //如果该节点是根节点
		{
			_root = subL;		
			_root->_parent = nullptr;
		}
		else  //不平衡节点只是一棵子树
		{
			if (ppNode->_left == parent)  //如果旧父节点等于爷爷节点的左节点,新父节点为爷爷节点的左节点
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;	//新父节点指向爷爷节点。
		}
		parent->_bf = subL->_bf = 0;  //只需要修改这两个的平衡因子
	}

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

	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)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right; 

		int bf = subLR->_bf;  //记录未旋转前subLR的平衡因子

		RotateL(parent->_left);  
		RotateR(parent);
	
		if (bf == -1)  //如果bf为-1,即插入在subLR的左边
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1) //插入在subLR的右边
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height() //树的高度
	{
		return _Height(_root);
	}

	int Size()  //插入的节点个数
	{
		return _Size(_root);
	}

private:
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

	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	leftHeight = _Height(root->_left);
		int	rightHeight = _Height(root->_right);
		//如果不平衡
		if (abs(leftHeight - rightHeight) >= 2)
		{
			cout << root->_kv.first << endl;
			return false;
		}

		//检查平衡因子是否正确
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << endl;
			return false;
		}

		return _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	
	
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

private:
	Node* _root=nullptr; 
};

void AVLTreeTest1()
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int,int> t1;
	for (auto e : a)
	{
		t1.Insert({e,e});

		cout <<"Insert:"<<e<<"->"<< t1.IsBalance() << endl;
	}

	t1.InOrder();	

	cout << t1.IsBalance() << endl;
}

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

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

相关文章

Spring的AOP基础以及AOP的核心概念

2. AOP基础 学习完spring的事务管理之后&#xff0c;接下来我们进入到AOP的学习。 AOP也是spring框架的第二大核心&#xff0c;我们先来学习AOP的基础。 在AOP基础这个阶段&#xff0c;我们首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c;让大家快速体…

高级RAG检索中的五种查询重写策略_用于检索增强的大型语言模型的查询重写

一、前言 检索增强生成 (RAG) 作为人工智能 (AI) 领域的一项重要技术&#xff0c;近年来得到了飞速发展。它将基于检索模型和基于生成的模型相结合&#xff0c;利用海量外部数据&#xff0c;生成更具信息量、更准确、更具语境相关性的回复。检索策略是 RAG 系统的关键组成部分…

2024年最适合高级网工的11款Linux

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 你们好&#xff0c;我的网工朋友。 Linux作为一个免费且开源的操作系统&#xff0c;随着时间的推移催生了多个发行版&#xff0c;并且得到了庞大…

golang验证Etherscan上的智能合约

文章目录 golang验证Etherscan上的智能合约为什么要验证智能合约如何使用golang去验证合约获取EtherscanAPI密钥Verify Source Code接口Check Source Code Verification Status接口演示示例及注意事项网络问题无法调用Etherscan接口&#xff08;最重要的步骤&#xff09; golan…

应用层协议原理——因特网提供的运输服务

我们已经考虑了计算机网络能够一般性地提供的运输服务。现在我们要更为具体地考察由因特网提供的运输服务类型。因特网(更一般的是TCP/IP网络)为应用程序提供两个运输层协议&#xff0c;即UDP和TCP。当软件开发者为因特网创建一个新的应用时&#xff0c;首先要做出的决定是&…

js逆向案例 | 加速乐反爬逆向

前言 加速乐作为一种常见的反爬虫技术&#xff0c;在网络上已有大量详尽深入的教程可供参考。然而&#xff0c;对于那些初次接触的人来说&#xff0c;直接面对它可能仍会感到困惑。 声明 本文仅用于学习交流&#xff0c;学习探讨逆向知识&#xff0c;欢迎私信共享学习心得。如…

收银系统源码-商品报损管理

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…

ESXi6.7 update 3主机实现新硬件运行老环境

server 2003 SQL server 2000 SQL SP4 vmware tools 一、适用场景 1、运行多年的老企业&#xff0c;积累的数据量庞大&#xff0c;其中的数据库并不一定都是现在开发的平台或系统&#xff0c;而是已经正在运行&#xff0c;不能停业务的状态。 2、老系统老应用平台&#xf…

day01:项目概述,环境搭建

文章目录 软件开发整体介绍软件开发流程角色分工软件环境 外卖平台项目介绍项目介绍定位功能架构 产品原型技术选型 开发环境搭建整体结构&#xff1a;前后端分离开发前后端混合开发缺点前后端分离开发 前端环境搭建Nginx 后端环境搭建熟悉项目结构使用Git进行版本控制数据库环…

Day06-01-lvs

Day06-01-lvs 0. 核心内容1.负载均衡项目 选择故障: 2.lvs 预备姿势-arp3.lvs 概述4. lvs工作模式4.1 预备姿势4.2 lvs-dr模式4.3 lvs-nat模式4.4 小结 5. lvs-dr模式5.1 环境准备5.2 lvs-dr模式配置流程1) lvs服务端配置2) web服务器 RS服务端配置3) 小结4) 调试 5.3 抓包查看…

电脑清理软件用哪个好?这款工具有些饱受争议

电脑清理软件用哪个好?电脑作为我们工作和娱乐的重要工具&#xff0c;其运行速度和安全性直接影响着我们的生活质量。然而&#xff0c;随着时间的推移&#xff0c;电脑系统会积累大量垃圾文件、注册表错误、恶意软件等&#xff0c;导致电脑运行缓慢甚至崩溃。 这时&#xff0c…

el-tree 获取当前勾选节点的选中状态以及选中值对象 触发check-change多次事件问题原因

1.需求 现在需要一个树状结构的资产树 但是现在需求是 获取当前选中的值的状态是选中还是取消选中 然后再用当前选中 or 取消选中的值 进行 选中 or 取消选中的操作 一开始使用的是 check-change 方法 接收参数如图 但是我勾选父节点 或者 子节点后 他会打印一堆数据 是因…

基于Java+SpringMvc+Vue技术的智慧校园系统设计与实现

博主介绍&#xff1a;硕士研究生&#xff0c;专注于信息化技术领域开发与管理&#xff0c;会使用java、标准c/c等开发语言&#xff0c;以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年&#xff0c;拥有近12年的管理工作经验&#xff0c;拥有较丰富的技术架…

昇思25天学习打卡营第11天 | LLM原理和实践:基于MindSpore实现BERT对话情绪识别

1. 基于MindSpore实现BERT对话情绪识别 1.1 环境配置 # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore2.2…

geospy.AI 根据图片定位地理位置

文章目录 简介场景标志性建筑平凡的商店名标志性的物品标志性的地理位置标志性的街道难以分辨的古建筑不出名的山水 简介 GeoSpy.ai 作为一个地理空间情报平台&#xff0c;可以在以下场景中使用&#xff1a; 农业&#xff1a;监控农作物生长情况&#xff0c;预测产量&#xf…

课设:二手车交易管理系统(Java+MySQL)

简易数据库课程设计~分享 技术栈 本项目使用以下技术栈构建&#xff1a; Java: 作为主要编程语言&#xff0c;负责业务逻辑的实现。MySQL: 用于数据存储&#xff0c;管理用户、车辆和订单信息。JDBC: 用于Java与MySQL数据库之间的连接和操作。Swing GUI: 提供用户图形界面&am…

数据库课设---酒店管理系统(MySQL、VBNet)

目录 一. 知识技术 二. 需求分析 2.1 功能需求 2.2 数据需求 三. 数据流图与数据字典 3.1 数据流图 3.1.1 业务流图 3.1.2 数据流图 3.1.3 关系图 3.2 数据字典 四. 数据库设计 4.1 概念模型设计 4.2 逻辑模型设计 4.3 数据库实现 …

【LSB图像低位隐写】字符串隐写

按照自己的理解写了一个简单的字符串隐藏在图像中的python代码 前言 脱胎于内容安全的大作业~ 目前第一阶段&#xff0c;只完成了字符串隐藏在图像中 步过图像隐藏在图像应该异曲同工&#xff0c;之后实现~ 一、代码 图像的output路径和input路径写死了&#xff0c;这个需要…

【SpringBoot】IDEA查看spring bean的依赖关系

前因&#xff1a;研究springcloud config组件时&#xff0c;我发现config-server包下的EnvironmentController不在扫描的包路径下却可以响应客户端的请求&#xff0c;这引起了我的注意&#xff0c;我的问题是&#xff1a;EnvironmentController是怎么被添加进bean工厂的。本章就…

联系表单提交Contact Form自动发送邮件源码

联系表单Contact Form提交后自动发送邮件源码构建指南&#xff1f; 无论是商业网站、博客还是个人主页&#xff0c;几乎每个网站都会有一个联系表单&#xff0c;用于收集用户的反馈、询问或建议。AokSend将探讨联系表单在网站中的重要性&#xff0c;以及如何通过源码实现自动发…