【C++】AVL树(AVLTree)

news2024/10/10 15:34:52

目录

一、AVL树概念:

二、定义:

三、AVL树的插入:

四、AVL树的旋转:

1、左单旋:

2、右单旋:

3、右左双旋:

4、左右双旋:

五、AVL树的检验:


一、AVL树概念:

搜索二叉树的缺点:

二叉搜索树虽然正常来说其搜索效率不错,但是其结构不够严格导致其下限很低(趋近于链式结构的那种)这样的话其搜索就会相当于在顺序表中查找,效率变得很低,下限不能够保证。

于是下面两位前苏联学家就搞了AVL树:

解决方法:

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

于是在每一个节点中引入了平衡因子来记录这个节点的左右子树的高度差,

又引入了一个指针指向这个节点的父节点,方便旋转。

如下就是一个AVL树,蓝色的数字是这个节点的平衡因子(右子树的高度减左子树的高度)

二、定义:

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;

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

三、AVL树的插入:

思路:

1、毕竟是根据二叉搜索树修改的,在找待插入位置的时候可以按照二叉搜索树的插入方法。
2、找到待插入位置后,将待插入结点插入到树中。
3、更新平衡因子,如果出现不平衡(即_bf不是1/0/-1),则需要进行旋转。

 
二叉搜索树的插入规则:

1、待插入结点的key值比当前结点小就往该结点的左子树找。
2、待插入结点的key值比当前结点大就往该结点的右子树找。
3、待插入结点的key值与当前结点的key值相等就插入失败。

直到找到与待插入结点的key值相同的结点或者最终走到空节点位置进行插入。

插入完成后,

该节点的左右子树的高度发生了变化,就需要更新平衡因子,因此插入一个节点后,该节点和该节点的祖先节点的平衡因子也可能需要更新。

平衡因子的修改规则:

1、如果新插入的节点,在它的父节点的左边,它的父节点的平衡因子就--;

2、如果新插入的节点,在它的父节点的右边,它的父节点的平衡因子就++;

这个新节点的平衡因子就是0;

接着要判断它们的祖先是否要进行平衡因子的修改

这个时候多出来的指针parent就发挥很大的作用了

每修改完一个节点的平衡因子都要往上走找这个节点的parent

祖先的平衡因子的修改规则:

1、如果修改后其parent的平衡因子是1或-1就证明这个树影响了祖先,要继续往上找。

2、如果修改后其parent的平衡因子是0,就证明下面新增的节点补全了这个0平衡因子的节点的父节点的少的那一边树,就需要往上继续修改了

3、如果修改后其parent的平衡因子是2或-2,那么此时以parent为根的树就被破坏平衡了,那么就需要旋转

注意!!!!!!!!!!!

旋转之后平衡因子已经改好了,这个时候就需要退出循环了

bool Insert(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 < cur->_kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;
	
	while (parent)
	{
		//控制新节点的父节点的平衡因子
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else //if (cur == parent->_right)
		{
			parent->_bf++;
		}
		//向上修改祖先的平衡因子
		if (parent->_bf == 0)
		{
			break;
		}
		else if (abs(parent->_bf) == 1)
		{
			//继续往上走
			cur = parent;
			parent = parent->_parent;
		}
		else if (abs(parent->_bf) == 2)
		{
			//旋转
		}
        //防止出现其他情况
		else
		{
			assert(false);
		}
        break;
	}
	return true;
}

四、AVL树的旋转:

我们将插入节点的父节点记为cur,cur的父节点称为parent

当找到一个parent节点为2或-2的时候,就需要进行旋转了,此时cur必定为1或者-1,不可能为0,(毕竟是0就不会再往上走了),

这个时候旋转情况分为四种:

当parent的平衡因子为2,cur的平衡因子为1时,进行左单旋

当parent的平衡因子为-2,cur的平衡因子为-1时,进行右单旋
当parent的平衡因子为-2,cur的平衡因子为1时,进行左右双旋
当parent的平衡因子为2,cur的平衡因子为-1时,进行右左双旋

注意:

1、旋转的时候要保持它是搜索树始终

2、要变平衡并且降低这个子树的高度

1、左单旋:

新节点插入较高右子树的右侧

接下来上述h == 1的情况扩展为h == 2情况:

思路:

1、让cur的左子树成为parent的右子树

2、让parent成为cur的左子树

3、修改各自的父指针

4、让cur成为根或者让cur成为parent的原来父节点的子树(要提前保存parent的父节点)

5、更新平衡因子

6、h == 3及以上思路都是一样的

void RotateL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	parent->_right = curleft;
	if (curleft)
	{
		curleft->_parent = parent;
	}
	cur->_left = parent;

	//将parent的父节点保存起来
	Node* pparent = parent->_parent;
	parent->_parent = cur;

	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (pparent->_kv.first < cur->_kv.first)
		{
			pparent->_right = cur;
		}
		else //if (pparent->_kv.first > cur->_kv.first)
		{
			pparent->_left = cur;
		}
		cur->_parent = pparent;
	}
	parent->_bf = cur->_bf = 0;
}

2、右单旋:

新节点插入较高左子树的左侧:

下面就直接上h == 2情况:

思路:

1、让cur的右子树成为parent的左子树

2、让parent成为cur的右子树

3、修改各自的父指针

4、让cur成为根或者让cur成为parent的原来父节点的子树(要提前保存parent的父节点)

5、更新平衡因子

void RotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;

	parent->_left = curright;
	if (curright)
	{
		curright->_parent = parent;
	}
	cur->_right = parent;

	//将parent的父节点保存起来
	Node* pparent = parent->_parent;
	parent->_parent = cur;

	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (pparent->_kv.first < cur->_kv.first)
		{
			pparent->_right = cur;
		}
		else //if (pparent->_kv.first > cur->_kv.first)
		{
			pparent->_left = cur;
		}
		cur->_parent = pparent;
	}
	parent->_bf = cur->_bf = 0;
}

3、右左双旋:

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

如下图所示:

实际上是通过一次右单旋转化为能够进行一次左单旋就成的AVL树,这种旋转叫做右左双旋

思路:

1、在插入后向上找到对应的parent和cur,之后进行右单旋把这个折线变成直线。

2、根据平衡因子找到对应左单旋的parent和cur,进行左单旋

3、更新平衡因子

当旋转完成后,因为高度存在改变,所以就存在平衡因子的改变,如上最后一个图所示,不同的平衡因子的改变根据上述的80节点的初始平衡因子有关(毕竟新增节点是存在于80的左边或者是右边)分为三种情况(如下):

1、初始平衡因子是0,这个就证明这个节点是新增的,左右双旋后平衡因子都更新为0

2、初始平衡因子是1,左右双旋后平衡因子parent,cur,curleft更新为-1,0,0

3、初始平衡因子是-1,左右双旋后平衡因子parent,cur,curleft更新为0,1,0

void RotateRL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;
	int bf = curleft->_bf;

	RotateR(cur);

	RotateL(parent);

	//控制平衡因子
	if (bf == 1)
	{
		parent->_bf = -1;
		cur->_bf = 0;
		curleft->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		cur->_bf = 1;
		curleft->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curleft->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

4、左右双旋:

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

如下图所示:

实际上是通过一次左单旋转化为能够进行一次右单旋就成的AVL树,这种旋转叫做左右双旋

思路:

1、在插入后向上找到对应的parent和cur,之后进行左单旋把这个折线变成直线。

2、根据平衡因子找到对应右单旋的parent和cur,进行右单旋

3、更新平衡因子

或者是插在b上,这个和c是一样的。

当旋转完成后,因为高度存在改变,所以就存在平衡因子的改变,如上最后一个图所示,不同的平衡因子的改变根据上述的45节点的初始平衡因子有关(毕竟新增节点是存在于45的左边或者是右边)同右左双旋,分为三种情况(如下):

1、初始平衡因子是0,这个就证明这个节点是新增的,左右双旋后平衡因子都更新为0

2、初始平衡因子是1,左右双旋后平衡因子parent,cur,curleft更新为0,-1,0

3、初始平衡因子是-1,左右双旋后平衡因子parent,cur,curleft更新为1,0,0

	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		RotateL(cur);
		
		RotateR(parent);

		//控制平衡因子
		if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

五、AVL树的检验:

思路:

根据平衡因子的大小来检测AVL树的合法性,

平衡因子又是通过左右子树的高度差(右子树的高度 - 左子树的高度)来进行计算的

所以就需要写获得高度的函数,和检验是否是平衡树的函数,

因为在外面不能访问根节点,所以在内部就进行嵌套进而完成。

int Height()
{
	return _Height(_root);
}

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

	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

bool IsBalance()
{
	return _IsBalance(_root);
}

bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;

	int leftHight = Height(root->_left);
	int rightHight = Height(root->_right);

	if (rightHight - leftHight != root->_bf)
	{
		cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
		return false;
	}

	return abs(rightHight - leftHight) < 2
		&& IsBalance(root->_left) 
		&& IsBalance(root->_right);
}

10000个随机数进行插入:

运行效率挺快的,毕竟是绝对平衡的二叉搜索树,log_2(N),

查询的时候没啥问题,很快,但是如果是插入或者删除的时候,会进行旋转,性能就可能比较低了,所以,如果是查询高效且有序的数据结构并且数据不会改变就可以考虑使用AVL树

int main()
{
	int N = 10000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand());
	}
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	return 0;
}

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

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

相关文章

重离子+数字化,肿瘤治疗的新路

大国之重器&#xff0c;当为大国之民生。 医用重离子加速器装置&#xff0c;被誉为肿瘤放疗领域的大国重器。在河西走廊东侧的甘肃省武威肿瘤医院(以下简称&#xff1a;武威肿瘤医院)&#xff0c;我国首台拥有自主知识产权的医用重离子加速器装置坐落此。自建成投入使用以来&a…

新手教学系列——爬虫异步并发注意事项

引言 爬虫是网络数据采集中不可或缺的工具,很多程序员在入门时会遇到这样的问题:为什么我的爬虫这么慢?尤其在面对大量数据时,单线程爬虫的速度可能让人捶胸顿足。随着爬虫规模的增大,异步并发成为了提高爬取效率的关键。然而,异步并发并不像表面看起来那么简单,如果没…

有哪些可靠的算力租赁平台推荐

作为一名炼丹师&#xff0c;在炼丹过程中使用过不少 GPU 算力租用平台&#xff0c;也有很多心得体会~为了让道友们少走弯路&#xff0c;我打算把近期在市场上用过的几家热门的 GPU 算力平台比较比较&#xff0c;以便其他炼丹师选择适合自己的算力平台。话不多说看&#xff01; …

Python简介与入门

如果你要用计算机做很多工作&#xff0c;最后你会发现有一些任务你更希望用自动化的方式进行处理。比如&#xff0c;你想要在大量的文本文件中执行查找/替换&#xff0c;或者以复杂的方式对大量的图片进行重命名和整理。也许你想要编写一个小型的自定义数据库、一个特殊的 GUI …

2025年人工智能行业的发展趋势预测以及中小企业的应对策略

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 ​ 进入2024年的第四季度&#xff0c;人工…

python数据分析与可视化工具介绍-numpy库

NumPy&#xff08;Numerical Python的简称&#xff09;&#xff0c;是科学计算基础的一个库&#xff0c;提供了大量关于科学计算的相关功能&#xff0c;例如&#xff0c;线性变换&#xff0c;数据统计&#xff0c;随机数生成等。其提供的最核心的类型为多维数组类型&#xff08…

Android Studio 打包混淆失效问题

项目场景&#xff1a; 通过 Python 脚本运行打包 Apk &#xff0c;实现动态配置各个版本的 Apk。 问题描述 通过 Python 脚本打包编译 Apk&#xff0c;开启混淆后&#xff0c;打包成功&#xff0c;反编译出来的 Apk 并没有被混淆。 原因分析&#xff1a; 首先确认打包混淆…

矩阵系统源码搭建,oem贴牌,技术指导

一、技术选型与整合 多种技术的融合 矩阵系统通常需要整合多种技术&#xff0c;包括前端技术、后端技术、数据库技术、服务器技术等。选择合适的技术栈并确保它们能够良好地协同工作是一个挑战。例如&#xff0c;前端可能使用 React 或 Vue.js&#xff0c;后端可能使用 Java Sp…

聚观早报 | 台积电9月份营收;联发科发布天玑9400

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 10月10日消息 台积电9月份营收 联发科发布天玑9400 vivo X200系列将全系标配原子岛 骁龙8 Gen4或改名“骁龙8至尊…

[ComfyUI]看惯AI味女神,回归现实,聊聊去AI胶皮味现实真人写真

随着人工智能技术的飞速发展&#xff0c;图像生成与反推技术已经取得了显著的进展。然而&#xff0c;有时候我们也会怀念那些真实、自然的人像照片。今天&#xff0c;我们为您带来了一篇关于[ComfyUI]如何拍摄去AI胶皮味现实真人写真的文章&#xff0c;帮助您回归现实&#xff…

Redis:分布式 - 哨兵

Redis&#xff1a;分布式 - 哨兵 概念哨兵 Docker 搭建哨兵分布式选举流程 概念 Redis 的主从复制模式下&#xff0c;一旦主节点由于故障不能提供服务&#xff0c;需要人工进行主从切换&#xff0c;同时大量的客户端需要被通知切换到新的主节点上&#xff0c;对于上了一定规模…

美发店管理升级:SpringBoot技术实现

3系统分析 3.1可行性分析 通过对本美发门店管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本美发门店管理系统采用SSM框架&#xff0c;JAVA作为开发语…

产业园区数字化转型:破解传统园区发展瓶颈

在当今数字化浪潮汹涌澎湃的时代&#xff0c;传统产业园区面临着诸多发展瓶颈&#xff0c;而产业园区数字化转型成为了突破这些困境的关键路径。 传统产业园区往往存在着管理效率低下、资源配置不合理、企业间协同困难以及创新能力不足等问题。这些问题就像一道道枷锁&#xf…

胃阴亏则百病生!阴虚火旺、胃病多发、灼痛?秋冬滋补季,用“仙草”养养胃~

中国有句俗话“十人九胃”&#xff0c;现代社会作息混乱、饮食无度&#xff0c;胃病的患病率也变得越来越高。 经常熬夜&#xff0c;饮食不规律&#xff0c;暴饮暴食&#xff0c;肠胃不适&#xff0c;面色差~ 喜食辛辣&#xff0c;总吃烧烤&#xff0c;烘烤、辛辣热性的食物&…

线下旅行社增长:单品牌半年新开500家门店,净利润增幅超307%

实体门店集体下行&#xff0c;旅行社缘何能越开越多 前言 线上流量井喷背景下&#xff0c;旅游业却“反其道而行”&#xff0c;重燃线下旅行社战火。 文化和旅游部市场管理司今年9月发布的《2024年第二季度全国旅行社统计调查报告》显示&#xff0c;截至2024年6月30日&#…

WPF中的常用控件

控件分类 在第一篇文章.Net Core和WPF介绍中的WPF的功能和特性部分根据功能性介绍了WPF的控件 名称。 在接下来的文章中&#xff0c;将会详细的介绍各个控件的概念及使用。 主要包括&#xff1a; 内容控件&#xff1a;Label、Button、CheckBox、ToggleButton、RadioButton、…

Large AI Model Empowered Multimodal Semantic Communications——基于大模型的多模态语义通信框架

1. 背景 本文讨论了大规模AI模型在多模态语义通信&#xff08;SC&#xff09;系统中的应用&#xff0c;处理不同类型的数据&#xff08;文本、音频、图像、视频&#xff09;以实现低延迟、高质量的语义层次通信。提出了一个基于大模型的多模态SC&#xff08;LAM-MSC&#xff09…

心理学基础

一&#xff0c;三观 三观包括世界观、人生观、价值观。 1&#xff0c;世界观 世界观‌是人们对整个世界的总的看法和根本观点。可以划分为两种根本对立的世界观类型&#xff0c;即唯心主义世界观和唯物主义世界观。 2&#xff0c;人生观 人生观是人们在实践中形成的对于人…

[C高手编程] C语言数据结构:排序算法与查找算法

&#x1f496;&#x1f496;⚡️⚡️专栏&#xff1a;C高手编程-面试宝典/技术手册/高手进阶⚡️⚡️&#x1f496;&#x1f496; 「C高手编程」专栏融合了作者十多年的C语言开发经验&#xff0c;汇集了从基础到进阶的关键知识点&#xff0c;是不可多得的知识宝典。如果你是即将…

开发一个exe应用工具,pdf转图片工具,pdf截成单个图片,然后全量修整没用的白边

pdf转图片工具 图片裁剪白边工具 window系统exe应用工具下载地址https://download.csdn.net/download/websmallrabbit/89864476如果有帮助到您还请动动手帮忙点赞&#xff0c;关注&#xff0c;评论转发&#xff0c;感谢啦&#xff01;&#x1f495;&#x1f495;&#x1f495;&…