【数据结构】AVL树(平衡二叉树)

news2024/9/9 4:08:49

目录

  • 一、AVL树的概念
  • 二、AVL树的节点
  • 三、AVL树的插入
  • 四、AVL树的旋转
    • 1.插入在较高左子树的左侧,使用右单旋
    • 2.插入在较高右子树的右侧,使用左单旋
    • 3.插入较高左子树的右侧,先左单旋再右单旋
    • 4.插入较高右子树的左侧,先右单旋再左单旋
  • 五、AVL树的验证
  • 六、AVL树的删除
  • 七、讲讲AVL树的性能

前言:
在前面我们学习了平衡二叉树,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现,下面我们要学习的AVL树就是处理二叉树自身缺陷的一种方式
在这里插入图片描述

一、AVL树的概念

在平衡二叉树中,如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素的时间复杂度会退化成O(N)。
因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树可以是一颗空树,如果不是,则它(或者它的任何一颗子树)需要具备以下性质:

  1. 它的左右孩子都是AVL树
  2. 左右子树的高度之差(平衡因子)的绝对值不超过1

如果它有n个节点,它的高度可以保持在log_n,时间复杂度为O(log_n)

二、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)
		, _bf(0)
		, _kv
	{}
};

三、AVL树的插入

总的来说,AVL树就是在二叉搜索树的基础上加了一个平衡因子,所以AVL树的插入分两步:

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

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	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->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	while (parent)
	{
	    // 更新双亲的平衡因子
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}
		//没有改变高度,不继续向上更新平衡因子
		if (parent->_bf == 0)
		{
			break;
		}
       // 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者                 -1 ,说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以Parent为根的树进行旋转处理
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 旋转处理
			.......

			break;
		}
		else
		{
			// 插入之前AVL树就有问题
			assert(false);
		}
	}

	return true;
}

四、AVL树的旋转

当插入一个新节点时,若是平衡因子到了2,则会导致树的不平衡,此时我们就要对这棵树进行旋转操作,使其重新满足AVL树的性质。
根据节点插入位置,AVL树的旋转分四种:

1.插入在较高左子树的左侧,使用右单旋

在这里插入图片描述
上图在插入前,AVL树是平衡的,新节点插入到4的左子树(注意:此处不是左孩子)中,2左子树增加了一层,导致以1为根的二叉树不平衡,要让1平衡,只能将1左子树的高度减少一层,右子树增加一层, 即将左子树往上提,这样1节点转下来,因为1节点比2节点大,只能将其放在2的右子树,而如果2节点有右子树,右子树根的值一定大于2节点,小于1节点,只能将其放在1节点的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:

  1. **2节点的右孩子可能存在,也可能不存在
  2. 1节点可能是根节点,也可能是子树
    如果是根节点,旋转完成后,要更新根节点
    如果是子树,可能是某个节点的左子树,也可能是右子树**

说白了就是把5节点给1节点,再把1节点给2节点,就像是用手把1节点按下来一样(画图理解更佳)

代码:

void RotateR(Node* parent)//以1为旋转点,进行右单旋
{
	Node* subL = parent->_left;//2节点
	Node* subLR = subL->_right;//5节点,也可能没有
	parent->_left = subLR;//5节点给1节点
	if (subLR)
	{
		subLR->_parent = parent;
	}
	subL->_right = parent;
	//1节点可能是根节点,也可能是子树
    // 如果是根节点,旋转完成后,要更新根节点
    // 如果是子树,可能是某个节点的左子树,也可能是右子树
	Node* ppnode = parent->_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 = 0;
	parent->_bf = 0;
}

2.插入在较高右子树的右侧,使用左单旋

在这里插入图片描述
参考上面的右单旋,代码:

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;
		subR->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subR;
		}
		else
		{
			ppnode->_right = subR;
		}
		subR->_parent = ppnode;
	}

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

3.插入较高左子树的右侧,先左单旋再右单旋

在这里插入图片描述

将双旋变成单旋后再旋转,先以2节点为旋转点进行左单旋,然后再以1节点为旋转点进行右单旋,旋转完成后再考虑平衡因子的更新
代码:

void RorareLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进行调整
	int bf = subLR->_bf;
	// 先对2节点进行左单旋
	RorateL(parent->_left);
	// 再对对1节点进行右单旋
	RotateR(parent);


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

4.插入较高右子树的左侧,先右单旋再左单旋

在这里插入图片描述
和上面的先左再右差不多

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

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
    当subR的平衡因子为1,左单旋
    当subR的平衡因子为-1,右左单旋

  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
    当subL的平衡因子为1,左单旋
    当subL的平衡因子为-1,左右单旋

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

五、AVL树的验证

如何去验证我们写出来的是AVL树呢?
我们知道,AVL树是在二叉搜索树的基础上加入了控制高度平衡限制,所以对于验证AVL树,我们可以分两步:

  1. 验证是否为二叉搜索树
    对其来一次中序遍历,如果得到一个有序的序列,则为二叉搜索树

  2. 验证是否为平衡树
    (1) 各节点高度差的绝对值不超过1 (2)各节点的平衡因子是否正确

代码:

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

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

bool _IsBalance(Node* root, int& height)
{
	// 空树也是AVL树
	if (root == nullptr)
	{
		height = 0;
		return true;
	}

	int leftHeight = 0, rightHeight = 0;
	if (!_IsBalance(root->_left, leftHeight)
		|| !_IsBalance(root->_right, rightHeight))
	{
		return false;
	}

	if (abs(rightHeight - leftHeight) >= 2)
	{
		cout << root->_kv.first << "不平衡" << endl;
		return false;
	}

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

	height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

	return true;
}

bool IsBalance()
{
	int height = 0;
	return _IsBalance(_root, height);
}

六、AVL树的删除

AVL树也算是二叉搜索树,所以我们可以用二叉搜索树删除节点的方式进行删除(可以看看之前讲的二叉搜索树删除操作:https://blog.csdn.net/liuty0125/article/details/139508094?spm=1001.2014.3001.5501),不过有差别的是,在删除后还要更新删除后的平衡因子,最坏情况可能会更新到根节点,所以一般不会对AVL树进行删除操作

七、讲讲AVL树的性能

先说优点:AVL树是一个平衡的二叉搜索树,它的每个节点的左右子树高度差的绝对值不超过1,因此,哪怕最坏也只是查找高度次,保证了查询时高效的时间复杂度O(log_n)。

但是它的缺陷也很明显:如果我们要对AVL树做一些修改方面的操作时,它的性能就十分低了,因为在修改时我们还要维护它的绝对平衡,旋转的次数比较多,而且在删除时,最坏情况可能会更新到根节点。

所以,如果只是需要一种查询高效且有序的数据结构,且数据个数为静态(不改变),比较适合AVL树,但如果你需要经常修改的话,AVL树可能就不太适合了。

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

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

相关文章

论文研读 A Comparison of TCP Automatic Tuning Techniques for Distributed Computing

论文《分布式计算中TCP自动调优技术的比较》由Eric Weigle和Wu-chun Feng撰写&#xff0c;探讨了自动调整TCP缓冲区大小以提升分布式应用性能的不同方法。文章首先讨论了手动优化TCP缓冲区大小的局限性&#xff0c;并介绍了研究人员提出的各种自动调优技术来应对这些挑战。 作者…

打造智慧工厂核心:ARMxy工业PC与Linux系统

智能制造正以前所未有的速度重塑全球工业格局&#xff0c;而位于这场革命核心的&#xff0c;正是那些能够精准响应复杂生产需求、高效驱动自动化流程的先进设备。钡铼技术ARMxy工业计算机&#xff0c;以其独特的设计哲学与卓越的技术性能&#xff0c;正成为众多现代化生产线背后…

2024全国大学生数学建模竞赛优秀参考资料分享

0、竞赛资料 优秀的资料必不可少&#xff0c;优秀论文是学习的关键&#xff0c;视频学习也非常重要&#xff0c;如有需要请点击下方名片获取。 一、赛事介绍 全国大学生数学建模竞赛(以下简称竞赛)是中国工业与应用数学学会主办的面向全国大学生的群众性科技活动&#xff0c;旨…

⌈ 传知代码 ⌋ 记忆大师

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

MySQL 函数与约束

MySQL 函数与约束 文章目录 MySQL 函数与约束1 函数1.1 字符串函数1.2 数值函数1.3 日期函数1.4 流程函数 2 约束2.1 概述2.2 约束演示2.3 外键约束2.4 删除/更新行为 1 函数 函数是指一段可以直接被另一程序调用的程序或代码。 1.1 字符串函数 MySQL中内置了很多字符串函数&…

Python学习打卡:day02

day2 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 8、字符串的三种定义方式 字符串在Python中有多种定义形式 单引号定义法&#xff1a; name 黑马程序员双引号定义法&#xff1a; name "黑马程序…

攻防演练之-网络集结号

每一次的网络安全攻防演练都是各个安全厂商期待的网络安全盛会&#xff0c;因为目前的安全生态导致了只有在网络安全攻防演练期间&#xff0c;网络安全的价值才会走向台前&#xff0c;收到相关方的重视。虽然每一次都会由于各种原因不能如期举行&#xff0c;但是这一次的推迟总…

浏览器阻止屏幕息屏,js阻止浏览器息屏,Web网页阻止息屏

场景: 比如打开一个浏览器页面(比如大屏),想让它一直显示着,而不是过几分钟不操作就屏幕黑了.(电脑有设置电脑不操作就会多长时间就会息屏睡眠,如果要求每个客户都去操作一下电脑设置一下从不睡眠,这很不友好和现实.而且我也只想客户在大屏的时候才这样,其他页面就正常,按电脑设…

eNSP学习——RIP的路由引入

目录 主要命令 原理概述 实验目的 实验内容 实验拓扑 实验编址 实验步骤 1、基本配置 2、搭建公司B的RIP网络 3、优化公司B的 RIP网络 4、连接公司A与公司B的网络 需要eNSP各种配置命令的点击链接自取&#xff1a;华为&#xff45;NSP各种设备配置命令大全PDF版_ensp…

RAG检索与生成的融合

1、rag定义 检索增强生成 (RAG) 模型代表了检索系统和生成模型两大不同但互补组件完美结合的杰作。通过无缝整合相关信息检索和生成与背景相关的响应&#xff0c;RAG模型在人工智能领域达到了前所未有的复杂程度。 2、rag工作流程 2.1、rag整体框架 query通过llm处理后&…

【Golang】Go语言中defer与return的精妙交织:探索延迟执行与返回顺序的微妙关系

【Golang】Go语言中defer与return的精妙交织&#xff1a;探索延迟执行与返回顺序的微妙关系 大家好 我是寸铁&#x1f44a; 总结了一篇defer 和 return 返回值 的执行顺序探讨的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 在Go语言中&#xff0c;defer 和return是两…

Codeforces Round 951 (Div. 2) 题解分享

A. Guess the Maximum 思路 贪心 毫无疑问的是&#xff0c;Alice会选择所有区间最大值的最小值-1&#xff0c;即。 关键是如何选取。我们注意到区间长度越大&#xff0c;这个区间的最大值是随着它不减的&#xff0c;所以如果Bob要让Alice选的最小的话&#xff0c;选择的区间…

1 c++多线程创建和传参

什么是进程&#xff1f; 系统资源分配的最小单位。 什么是线程&#xff1f; 操作系统调度的最小单位&#xff0c;即程序执行的最小单位。 为什么需要多线程&#xff1f; &#xff08;1&#xff09;加快程序执行速度和响应速度, 使得程序充分利用CPU资源。 &#xff08;2&…

FastAPI:在大模型中使用fastapi对外提供接口

通过本文你可以了解到&#xff1a; 如何安装fastapi&#xff0c;快速接入如何让大模型对外提供API接口 往期文章回顾&#xff1a; 1.大模型学习资料整理&#xff1a;大模型学习资料整理&#xff1a;如何从0到1学习大模型&#xff0c;搭建个人或企业RAG系统&#xff0c;如何评估…

Helm离线部署Rancher2.7.10

环境依赖&#xff1a; K8s集群、helm 工具 Rancher组件架构 Rancher Server 包括用于管理整个 Rancher 部署的所有软件组件。 下图展示了 Rancher 2.x 的上层架构。下图中&#xff0c;Rancher Server 管理两个下游 Kubernetes 集群 准备Rancher镜像推送到私有仓库 cat >…

RPA-UiBot6.0数据整理机器人—杂乱数据秒变报表

前言 友友们是否常常因为杂乱的数据而烦恼?数据分类、排序、筛选这些繁琐的任务是否占据了友友们的大部分时间?这篇博客将为友友们带来一个新的解决方案,让我们共同学习如何运用RPA数据整理机器人,实现杂乱数据的快速整理,为你的工作减负增效! 在这里,友友们将了…

SpringBoot3+Mybatis-Plus+h2数据库,入门Mybatis-Plus

SpringBoot3Mybatis-Plush2数据库&#xff0c;入门Mybatis-Plus mybatis-plus官网地址maven依赖数据库脚本配置文件实体类Mapper入门程序启动程序测试单元测试测试结果 Service层接口service层接口单元测试测试结果 项目结构 mybatis-plus官网地址 https://www.baomidou.com/ …

RocketMq源码解析六:消息存储

一、消息存储核心类 rocketmq消息存储的功能主要在store这个模块下。 核心类就是DefaultMessageStore。我们看下其属性 // 配置文件 private final MessageStoreConfig messageStoreConfig; // CommitLog 文件存储实现类 private final CommitLog commitLog; …

微信小程序学习笔记(1)

文章目录 一、文件作用app.json&#xff1a;project.config.json:sitemap.json页面中.json 二、项目首页三、语法**WXML**和**HTML**WXSS 和CSS的区别小程序中.js文件的分类 一、文件作用 app.json&#xff1a; 当前小程序的全局配置&#xff0c;包括所有页面路径、窗口外观、…

[Cloud Networking] Layer3 (Continue)

文章目录 1. DHCP Protocol1.1 DHCP 三种分配方式1.2 DHCP Relay (中继) 2. 路由协议 (Routing Protocol)2.1 RIP (Routing Information Protocol)2.2 OSPF Protocol2.3 BGP Protocol2.4 IS-IS Protocol2.5 ICMP&#xff08;Internet Control Message Protocol&#xff09; 1. …