【C++】17.AVL树

news2024/11/16 2:50:00

一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

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

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

在这里插入图片描述
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)

二、AVL树的定义

template<class K,class V>
struct AVLTreeNode
{
	struct AVLTreeNode* _left;//左孩子
	struct AVLTreeNode* _right;//右孩子
	struct AVLTreeNode* _parent;//父节点
	int _bf;//平衡因子:右子树高度-左子树高度
	pair<K, V> _kv;

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

三、AVL树的插入

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

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

通过画图分析我们知道插入节点可能会导致父节点的平衡因子发生改变:

插入在左子树上,平衡因子- -;插入在右子树上,平衡因子++

而更新完平衡因子后又有如下情况:

  • parent的平衡因子为0
    说明更新前 parent 的平衡因子是 1 或 -1 ,而新的节点插入在矮的那边,parent 所在子树高度不变,不需要继续向上更新
  • parent的平衡因子为±1
    说明更新前 parent 的平衡因子是 0 ,新节点无论插入在哪一边, parent 所在子树高度都会发生改变,都需要继续向上检查是否需要更新
  • parent的平衡因子为±2
    说明更新之前 parent 的平衡因子是 1 或 -1 ,新节点插入在本身就高的那一边,进一步导致了 parent 所在子树的不平衡,表示需要旋转以适应规则
bool Insert(pair<K, V> kv)
{
	//寻找新结点插入位置
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* cur = _root;
	while (cur)
	{
		if (kv.first < cur->_kv.first)
			cur = cur->_left;
		else if (kv.first > cur->_kv.first)
			cur = cur->_right;
		else
			return false;
	}

	//插入新结点
	Node* parent = cur->_parent;
	cur = new Node(kv);
	cur->_parent = parent;
	if (cur = parent->_left)
		parent->_left = cur;
	else
		parent->_right = cur;

	//更新平衡因子
	while (parent)
	{
		//插入右边就++,插入左边就--
		if (cur = parent->_left)
			parent->_bf--;
		else
			parent->_bf++;

		//平衡因子此时为0,就说明原来是±1,插入之后平衡因子变为0了
		if (parent->_bf == 0)
		{
			break;
		}
		//平衡因子此时为±1,需要判断祖先是否需要更新
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = cur->_parent;
		}
		//平衡因此此时为±2,说明插入的新节点加剧了不平衡,因此需要旋转
		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)
				RotateRL(parent);
			else if (parent->_bf == -2 && cur->_bf == 1)
				RotateLR(parent);
			else
				assert(0);
			break;
		}
		//平衡因子为其他值说明上述逻辑有错误
		else
		{
			assert(0);
		}
	}
	return true;
}

四、AVL树的旋转

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

4.1 左单旋

在这里插入图片描述
结论:当parent节点的平衡因子为1,parent->_parent节点的平衡因子为2时需要向左单旋。

void RotateL(Node* parent)
{
	Node* parentR = parent->_right;
	Node* parentRL = parentR->_left;

	Node* parentParent = parent->_parent;//保存父节点的父节点

	//重新链接
	parent->_right = parentRL;
	if (parentRL)
		parentRL->_parent = parent;

	parentR->_left = parent;
	parent->_parent = parentR;

	//判断父节点是否为根节点
	if (parentParent == nullptr)
	{
		_root = parentR;
		parentR->_parent = nullptr;
	}
	else
	{
		parentR->_parent = parentParent;
		if (parent == parentParent->_left)
			parentParent->_left = parentR;
		else
			parentParent->_right = parentR;
	}

	parent->_bf = parentR->_bf = 0;//更新平衡因子
}

4.2 右单旋

在这里插入图片描述
结论:当parent节点的平衡因子为-1,parent->_parent节点的平衡因子为-2时需要向右单旋。

void RotateR(Node* parent)
{
	Node* parentL = parent->_left;
	Node* parentLR = parentL->_right;

	Node* parentParent = parent->_parent;//保存父节点的父节点

	//重新链接
	parent->_left = parentLR;
	if (parentLR)
		parentLR->_parent = parent;

	parentL->_right = parent;
	parent->_parent = parentL;

	if (parentParent == nullptr)
	{
		_root = parentL;
		parentL->_parent = nullptr;
	}
	else
	{
		parentL->_parent = parentParent;
		if (parent == parentParent->_left)
			parentParent->_left = parentL;
		else
			parentParent->_right = parentL;
	}

	parent->_bf = parentL->_bf = 0;
}

4.3 左右双旋

在这里插入图片描述
结论:当parent节点的平衡因子为1,parent->_parent节点的平衡因子为-2时需要左右双旋

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

	RotateL(parentL);
	RotateR(parent);

	if (bf == 0)
	{
		parent->_bf = 0;
		parentL->_bf = 0;
		parentLR->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		parentL->_bf = -1;
		parentLR->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		parentL->_bf = 0;
		parentLR->_bf = 0;
	}
	else
		assert(0);
}

4.4 右左双旋

在这里插入图片描述
结论:当parent节点的平衡因子为-1,parent->_parent节点的平衡因子为2时需要右左双旋

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

	RotateR(parentR);
	RotateL(parent);

	if (bf == 0)
	{
		parent->_bf = 0;
		parentR->_bf = 0;
		parentRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		parentR->_bf = 0;
		parentRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		parentR->_bf = 1;
		parentRL->_bf = 0;
	}
	else
		assert(0);
}

总结:
假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑:

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为parentR,
    • 当parentR的平衡因子为1时,执行左单旋
    • 当parentR的平衡因子为-1时,执行右左双旋
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为parentL,
    • 当parentL的平衡因子为-1是,执行右单旋
    • 当parentL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新

五、AVL树的验证

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

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

int Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int left = Height(root->_left);
	int right = Height(root->_right);

	return left > right ? left + 1 : right + 1;
}

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

	int LeftHeight = Height(root->_left);
	int RightHeight = Height(root->_right);

	int diff = RightHeight- LeftHeight ;
	if (diff != root->_bf || abs(LeftHeight - RightHeight) > 1)
		return false;
	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

六、AVL树的性能

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

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

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

相关文章

Linux实用操作二

文章目录 Linux实用操作二日期、时区&#xff1a;date命令查看日期时间作用&#xff1a;语法&#xff1a;字段解释&#xff1a;操作&#xff1a; 修改Linux系统时区作用&#xff1a;操作&#xff1a; 使用ntp进行时间同步和校准作用&#xff1a;操作&#xff1a; IP地址、主机名…

buuctf web 第五到八题

[ACTF2020 新生赛]Exec 这里属实有点没想到了&#xff0c;以为要弹shell&#xff0c;结果不用 127.0.0.1;ls /PING 127.0.0.1 (127.0.0.1): 56 data bytes bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var127.0.0.1;tac /f*[GXYCTF2019]Pin…

【数学建模】多波束测线问题(持续更新)

多波束测线问题 问题 1建立模型覆盖宽度海水深度重叠长度重叠率 问题二问题三问题四 问题 1 与测线方向垂直的平面和海底坡面的交线构成一条与水平面夹角为 α \alpha α的斜线&#xff08;如下图&#xff09;&#xff0c;称 α \alpha α为坡度。请建立多波束测深的覆盖宽度及…

世界华人国学泰斗级人物颜廷利:人类史上全球公认最伟大的思想家

世界华人国学泰斗级人物颜廷利:人类史上全球公认最伟大的思想家 颜廷利教授,一位在21世纪东方哲学、科学界具有深远影响力的人物,同时也是当代中国教育界的知名教授、周易起名与易经姓名学的专家,以及现代国学的杰出代表。他在其著作《升命学说》中提出了一系列独到的理论,包括…

Mybatis--分页查询

一、分页查询 分页查询则是在页面上将本来很多的数据分段显示&#xff0c;每页显示用户自定义的行数。可提高用户体验度&#xff0c;同时减少一次性加载&#xff0c;内存溢出风险。 1、真假分页 分页分为&#xff1a;真分页和假分页。 假分页&#xff1a;一次性查询所有数据存入…

笔记小结:卷积神经网络之多输入多输出通道

本文为李沐老师《动手学深度学习》笔记小结&#xff0c;用于个人复习并记录学习历程&#xff0c;适用于初学者 彩色图像具有标准的RGB通道来代表红、绿和蓝&#xff0c;需要三个通道表示&#xff0c;故而只有单输入单输出是不够的。 对于单个输入和单个输出通道的简化例子&…

Yolo-World网络模型结构及原理分析(一)——YOLO检测器

文章目录 概要一、整体架构分析二、详细结构分析YOLO检测器1. Backbone2. Head3.各模块的过程和作用Conv卷积模块C2F模块BottleNeck模块SPPF模块Upsampling模块Concat模块 概要 尽管YOLO&#xff08;You Only Look Once&#xff09;系列的对象检测器在效率和实用性方面表现出色…

【引领未来智造新纪元:量化机器人的革命性应用】

在日新月异的科技浪潮中&#xff0c;量化机器人正以其超凡的智慧与精准的操作&#xff0c;悄然改变着各行各业的生产面貌&#xff0c;成为推动产业升级、提升竞争力的关键力量。今天&#xff0c;让我们一同探索量化机器人在不同领域的广泛应用价值&#xff0c;见证它如何以科技…

CSA笔记4-包/源管理命令以及本地光盘仓库搭建

包/源管理命令 1.rpm是最基础的rmp包的安装命令&#xff0c;需要提前下载相关安装包和依赖包 2.yum/dnf是基于rpm包的自动安装命令&#xff0c;可以自动在仓库中匹配安装软件和依赖包 注意:以上是安装命令&#xff0c;以下是安装源 3.光盘源&#xff1a;是指安装系统时后的…

Air780EP- AT开发-阿里云应用指南

简介 使用AT方式连接阿里云分为一机一密和一型一密两种方式&#xff0c;其中一机一密又包括HTTP认证二次连接和MQTT直连两种方式 关联文档和使用工具&#xff1a; AT固件获取在线加/解密工具阿里云平台 准备工作 Air780EP_全IO开发板一套&#xff0c;包括天线SIM卡&#xff0…

华为云技术精髓笔记(四)-CES基础入门实战

华为云技术精髓笔记(四) CES基础入门实战 一、监控ECS性能 1、 远程登录ECS 步骤一 双击实验桌面的“Xfce终端”打开Terminal&#xff0c;输入以下命令登录云服务器。注意&#xff1a;请使用云服务器的公网IP替换命令中的【EIP】。 LANGen_us.UTF-8 ssh rootEIP说明&#x…

中国自然灾害影响及损失数据

自然灾害往往会导致大量的人员伤亡和财产损失&#xff0c;数据集详细记载了2014-2020年中国自然灾害影响以及灾害造成的损失情况。其中包括地震、台风、雨雪、阵雨、雪灾、暴雨、旱灾、龙卷风、泥石流、山崩、泥石流、滑坡、洪涝等灾害事件。 数据集主要以excel的格式存储。属性…

【学术会议征稿】第二届人工智能与自动化控制国际学术会议(AIAC 2024)

第二届人工智能与自动化控制国际学术会议&#xff08;AIAC 2024&#xff09; The 2nd International Conference on Artificial Intelligence and Automation Control 随着技术的迅猛发展&#xff0c;人工智能与自动化控制已经深入到工业、交通、医疗、教育等各个领域&#x…

【Linux】-----权限详解

目录 一、Linux下的权限概念 Ⅰ、是什么&#xff1f; Ⅱ、Linux下的两种角色 角色 如何添加普通用户 身份的转化方式 身份的提权 添加普通用户至白名单 二、Linux下的权限管理 Ⅰ、文件访问者的分类(Linux下的“人”) Ⅱ、文件类型和访问权限(事物属性) 1.文件类型 …

为什么阿里巴巴超级喜欢java开发?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 我猜可能是因为&#xff0…

GD32 MCU是如何进入中断函数的

用过GD32 MCU的小伙伴们都知道&#xff0c;程序是顺序执行的&#xff0c;但当有中断来的时候程序会跳转到中断函数&#xff0c;执行完中断函数后程序又继续回到原来的位置继续执行&#xff0c;那么你们知道MCU是如何找到中断函数入口的吗&#xff1f; 今天我们就以GD32F303系列…

MacOS M1 安装item2 并配置Zsh

文章目录 1 下载item22 美化item22.1 配置主题2.2 设置黑色的主题&#xff1a;2.3 配置显示状态栏 status bar 3 安装 Oh my zsh3.1 设置主题3.2 设置插件3.3 安装第三方插件1 下载仓库解压2 使用 git clone 一些常用插件以及其作用 参考 1 下载item2 MacOS自带终端&#xff0…

springboot个体快餐订单系统-计算机毕业设计源码13441

目 录 摘要 1 绪论 1.1 研究背景 1.2研究意义 1.3论文结构与章节安排 2 个体快餐订单系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 个…

ATA-7025高压放大器的参数特点与应用领域有哪些

高压放大器是一种电子设备&#xff0c;用于将低电压信号放大成高电压信号&#xff0c;其参数特点与应用领域有着广泛的应用。本文将从高压放大器的参数特点和主要应用领域两个方面展开详细介绍。 高压放大器的参数特点主要包括输入输出电压范围广、带宽宽、增益高、输出功率大等…

计网:物理层

写在开头&#xff1a;物理层就负责传送比特0和1&#xff0c; 本质上理解物理层就是理解传输介质哪个表示比特0和1&#xff0c;如&#xff1a;高电平表示1、低电平表示0等 物理层主要任务&#xff1a; 机械特性&#xff1a;指明接口所用接线器的形状和尺寸、引脚数目和排列、固…