用平衡因子实现-AVL树

news2024/11/23 22:51:35

目录

  • AVL树的概念
  • AVL树节点的定义
  • AVL树的插入
  • AVL树的旋转
    • 左单旋(parent->_bf == 2 && cur->_bf == 1)
      • a,b,c当高度为0
      • a,b,c当高度为1
      • a,b,c当高度为2
      • a,b,c当高度为......
    • 右单旋(parent->_bf == -2 && cur->_bf == -1)
      • a,b,c当高度为0
      • a,b,c当高度为1
      • a,b,c当高度为2
      • a,b,c当高度为........
  • 左右双旋(parent->_bf == -2 && cur->_bf == 1)
      • a,b,c当高度为0
      • a,b,c当高度为1
      • a,b,c当高度为2
      • a,b,c当高度为.......
  • 右左双旋(parent->_bf == 2 && cur->_bf == -1)
      • a,b,c当高度为0
      • a,b,c当高度为1
      • a,b,c当高度为2
      • a,b,c当高度为.......
  • AVL树的性能

AVL树的概念

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

为什么左右高度差不是相等呢?

因为有些结点数量是永远不能保证高度差相等的比如2个,4个… 所以只能退而求其次,左右高度差不超过1.。

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

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
  • 每个子树也都要遵守左右子树高度之差的绝对值不超过1
  • AVL树的实现不一定必须要用平衡因子,平衡因子只是它的一种实现方式,个人感觉平衡因子比较容易理解AVL树

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

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)//新插入的结点,没有左右子树,所以平衡因子为0
	{}
};

AVL树的插入

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

  1. 按照二叉搜索树的方式插入新节点
  2. 更新平衡因子
  3. 如果更新完以后,平衡因为没有出现问题 ∣ b f ∣ < = 1 |bf| <= 1 bf<=1,平衡结构没有受到影响,不需要处理
  4. 如果更新完以后,平衡出现问题 ∣ b f ∣ > 1 |bf|> 1 bf>1,平衡结构受到影响, 需要处理(旋转)

在这里插入图片描述
🔍 插入新增节点,会影响祖先的平衡因子(全部或者部分)

  1. 新增结点在parent左边 cur == parent->_left) parent->_bf--
  2. 新增结点在parent右边cur == parent->_right parent->_bf++

🔍 每增加一个结点,就要检查parent所在的子树高度是否变化? 变了继续更新,不变则不再更新。

  1. 新增结点后,如果parent的平衡因子等于1或者-1,parent所在的子树高度变了,需要继续向上更新平衡因子。为什么呢?因为在插入结点之前parent的平衡因子等于0,说明插入之前左右两个子树高度相等,现在parent一边高一边低,高度发生变化需要继续向上更新平衡因子。
  2. 新增结点后,如果parent的平衡因子等于2或者-2,parent所在的子树高度不平衡了,需要处理这个子树(进行旋转)。
  3. 新增结点后,如果parent的平衡因子等于0,parent所在的子树高度不变,不需要处理继续向上更新,这一次插入结束。为什么呢?说明插入之前parent的平衡因子等于1或者-1,插入之前parent一边高一边低,插入的结点填在了矮的那一边,它的高度不变。
bool Insert(const pair<K, V>& kv)
{
	if (nullptr == _root)//第一次插入
	{
		_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 < cur->_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 == 1 || parent->_bf == -1)
		{
			parent = parent->_parent;
			cur = cur->_parent;
		}
		else if (parent->_bf == 0)
		{
			break;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 需要旋转处理 -- 1、让这颗子树平衡 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)
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				//说明这个AVL树存在问题
				assert(false);
			}

			break;
		}
		else
		{
			//说明这个AVL树存在问题
			assert(false);
		}
	}

	return true;
}

AVL树的旋转

💡旋转的原则:保持他继续是搜索树。
💡旋转的目的:左右均衡,降低整棵树的高度。
🔍如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

左单旋(parent->_bf == 2 && cur->_bf == 1)

在这里插入图片描述

a,b,c当高度为0

在这里插入图片描述

a,b,c当高度为1

在这里插入图片描述

a,b,c当高度为2

在这里插入图片描述

在这里插入图片描述

a,b,c当高度为…

后面的情况就不画了情况太多了。只要符合parent->_bf == 2 && cur->_bf == 1的子树和根都可以进行左单旋。
💻左单旋代码

//右子树高,进行左单旋
void RotateL(Node* parent)
{
	Node* childR = parent->_right;
	Node* childRL = childR->_left;

	parent->_right = childRL;
	if (childRL)//如果childRL不为空,要链接父节点
		childRL->_parent = parent;

	Node* pparent = parent->_parent;
	childR->_left = parent;
	parent->_parent = childR;

	if (nullptr == pparent)//说明是根结点
	{
		_root = childR;
		//_root->_parent = nullptr;
		childR->_parent = nullptr;
	}
	else
	{
		//判断是pparent的左子树还是右子树
		if (pparent->_left == parent)
		{
			pparent->_left = childR;
		}
		else
		{
			pparent->_right = childR;
		}

		childR->_parent = pparent;//链接父结点
	}

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

右单旋(parent->_bf == -2 && cur->_bf == -1)

在这里插入图片描述

a,b,c当高度为0

在这里插入图片描述

a,b,c当高度为1

在这里插入图片描述

a,b,c当高度为2

在这里插入图片描述
在这里插入图片描述

a,b,c当高度为…

后面的情况就不画了情况太多了。只要符合parent->_bf == -2 && cur->_bf == -1的子树和根都可以进行右单旋。
💻右单旋代码

//左子树高,进行右单旋
void RotateR(Node* parent)
{
	Node* childL = parent->_left;
	Node* childLR = childL->_right;

	parent->_left = childLR;
	if (childLR)
		childLR->_parent = parent;

	Node* pparent = parent->_parent;
	childL->_right = parent;
	parent->_parent = childL;

	if (_root == parent)//说明是根结点
	{
		_root = childL;
		_root->_parent = nullptr;
	}
	else
	{
		//判断是pparent的左子树还是右子树
		if (pparent->_left == parent)
		{
			pparent->_left = childL;
		}
		else
		{
			pparent->_right = childL;
		}

		childL->_parent = pparent;//链接父结点
	}

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

左右双旋(parent->_bf == -2 && cur->_bf == 1)

在这里插入图片描述

a,b,c当高度为0

在这里插入图片描述

a,b,c当高度为1

在这里插入图片描述

a,b,c当高度为2

在这里插入图片描述

a,b,c当高度为…

后面的情况就不画了情况太多了。只要符合parent->_bf == -2 && cur->_bf == 1`的子树和根都可以进行左右双旋。
💻左右双旋代码

//左子树的右子树高,进行左右双旋
void RotateLR(Node* parent)
{
	Node* childL = parent->_left;
	Node* childLR = childL->_right;
	int bf = childLR->_bf;//bf有三种情况

	RotateL(parent->_left);
	RotateR(parent);

	if (bf == 1)//bf == 1代表新增加结点是childLR的左子树
	{
		parent->_bf = 0;
		childLR->_bf = 0;
		childL->_bf = -1;
	}
	else if (bf == -1)//bf == -1代表新增加结点是childLR的右子树
	{
		parent->_bf = 1;
		childLR->_bf = 0;
		childL->_bf = 0;
	}
	else if (bf == 0)//bf == 0代表新增加结点是childLR,使平衡因子发生错误
	{
		parent->_bf = 0;
		childLR->_bf = 0;
		childL->_bf = 0;
	}
	else
	{
		assert(false);
	}
	
}

右左双旋(parent->_bf == 2 && cur->_bf == -1)

在这里插入图片描述

a,b,c当高度为0

在这里插入图片描述

a,b,c当高度为1

在这里插入图片描述

a,b,c当高度为2

在这里插入图片描述

a,b,c当高度为…

后面的情况就不画了情况太多了。只要符合parent->_bf == 2 && cur->_bf == -1的子树和根都可以进行右左双旋。

💻右左双旋代码

//右子树的左子树高,进行右左双旋
void RotateRL(Node* parent)
{
	Node* childR = parent->_right;
	Node* childRL = childR->_left;
	int bf = childRL->_bf;//bf有三种情况

	RotateR(parent->_right);
	RotateL(parent);

	if (bf == 1)
	{
		parent->_bf = -1;
		childR->_bf = 0;
		childRL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		childR->_bf = 1;
		childRL->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		childR->_bf = 0;
		childRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

AVL树的性能

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

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

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

相关文章

UE5《Electric Dreams》项目PCG技术解析 之 基于关卡PCGSettings的工作流

程序化内容生成框架(Procedural Content Generation Framework,下文简称PCG)可谓是UE5.2版本中最令人瞩目的新技术之一&#xff0c;老王也是在第一时间就关注了这个技术&#xff0c;以前尝试过用Houdini来实现UE5中的程序化内容生成&#xff0c;感觉还是很蹩脚的&#xff0c;毕…

无线发射功率

无线电发射功率 无线电发射机输出的射频信号&#xff0c;通过馈线&#xff08;电缆&#xff09;输送到天线&#xff0c;由天线以电磁波形式辐射出去。电磁波到达接收地点后&#xff0c;由天线接收下来&#xff08;仅仅接收很小很小一部分功率&#xff09;&#xff0c;并通过馈…

Linux系统编程(再论execve)

文章目录 前言一、execve的第三个参数二、进程空间三、命令行参数规范四、optstring规则的扩展定义总结 前言 本篇文章我们继续来研究一下execve这个系统调用&#xff0c;上篇文章已经讲解了前两个参数的意义了&#xff0c;那么这篇文章就来讲解一下第三个参数的具体含义。 一…

三分钟学习一个python小知识4-----------我的对python中numpy的理解, 我列举了关于numpy常用的10个例子来深入理解numpy

这里写目录标题 1、NumPy是什么2、NumPy的常见应用---必须掌握2.1.创建一个数组2.2.数组的属性2.3.取数组中的元素2.4.数组的运算2.5.数组的转置2.6. 数组的索引和切片2.7. 数组的重塑2.8. 数组的广播2.9. 数组的聚合操作2.10. 数组的排序 总结 1、NumPy是什么 NumPy是专门用于…

vue+elementui实现app布局小米商城,样式美观大方,功能完整

目录 一、项目效果在线预览 二、效果图 1.首页效果图 2.分类&#xff0c;动态分类商品数据根据所属分类动态切换 3.购物车&#xff0c;动态添加购物车&#xff08;增、删、改、查&#xff09; 4.我的 5.登录注册 6.商品详情 7.搜索&#xff08;动态模糊搜索、搜索历史…

12---整数转罗马数字

罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例…

文件上传漏洞-用骗子的角度分析

&#xff08;一&#xff09;文件上传漏洞 文件上传漏洞&#xff0c;发送在用户上传文件功能中&#xff0c;很多网址都对上传的文件格式进行限制&#xff0c;但是被攻击机者找到漏洞&#xff0c;把木马、病毒进行上传&#xff0c;然后控制服务器。对上传的文件格式不是不限制&a…

MySQL实战解析底层---为什么这些SQL语句逻辑相同,性能却差异巨大

目录 前言 案例一&#xff1a;条件字段函数操作 案例二&#xff1a;隐式类型转换 案例三&#xff1a;隐式字符编码转换 前言 在MySQL中&#xff0c;有很多看上去逻辑相同&#xff0c;但性能却差异巨大的SQL语句对这些语句使用不当的话&#xff0c;就会不经意间导致整个数据…

动态规划V (85、91、97)-最近都开始摆烂

CP85 最大矩形 题目描述&#xff1a; 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 学习记录&#xff1a; 对每一个地方都去统计最大举行的话&#xff0c;会有很多多余的计算&#xff0c;题…

windows操作系统线程结构体

上一篇我们介绍了进程结构体&#xff0c;这节我们介绍下线程结构体&#xff1a;ETHREAD。还是去windbg里面去看一下这个结构体的长相: 依旧是一大堆成员&#xff0c;我们只关注一些比较重要的结构体成员。在进程结构体中的第一个成员是一个子结构体Pcb,在线程结构体中&#xff…

【Simulink】基于FCS-MPC的带阻感负载的三相逆变器控制(Matlab Function)

之前写过三相并网逆变器FCS-MPC的博客 &#x1f449;【Simulink】基于FCS-MPC的三相并网逆变器控制&#xff08;Matlab Function&#xff09; 应用的对象是并网的&#xff0c;用一个电压源&#xff08;Three-Phase Programmable Voltage Source&#xff09;模拟交流电网。 本篇…

6.S081——设备中断与驱动部分(串口驱动与Console)——xv6源码完全解析系列(7)

0.briefly speaking 之前我们研究过Xv6中的陷阱机制&#xff0c;并搞懂了系统调用的全部流程&#xff0c;接下来我们以UART和console为研究对象&#xff0c;深入研读一下Xv6内核中有关设备中断驱动的代码&#xff0c;并对UART、shell、console、键盘、显示器等设备的协同运作过…

【算法总结】——排列型回溯

文章目录 排列型回溯例题1——46. 全排列例题2——N皇后 分析回溯时间复杂度的另一种技巧 排列型回溯 相比于组合&#xff0c;排列型回溯对于元素的顺序是有要求的。 为了告诉回溯下面还可以选择哪些数字&#xff0c;可以&#xff1a; 记录已经被选择的数字用一个集合存储还…

【Linux】16. 动静态库

1. 库概念的引出 但是如果只是单纯的将多个.o文件提供给使用者&#xff0c;那么如果.o文件过多链接就会变得非常复杂&#xff0c;于是我们考虑将所有的.o文件打包给使用者提供一个库文件即可。 库的本质就是.o文件的集合 2. 动静态库概念 在之前的学习过程中我们认识到动静态…

免费:5000个高清视频素材 (个人免费版权,含9个利基)

免费&#xff1a;5000个高清视频素材 (个人免费版权&#xff0c;含9个利基) 嘿&#xff01;你喜欢制作视频吗&#xff1f;总是在寻找一些酷炫的素材&#xff0c;但又担心会侵犯版权吗&#xff1f;别担心&#xff0c;我有一个超级好消息要告诉你&#xff01;现在&#xff0c;我…

代码随想录算法训练营第39天 | 62.不同路径 + 63.不同路径 II

今日任务 目录 62.不同路径 - Medium 63.不同路径 II - Medium 62.不同路径 - Medium 题目链接&#xff1a;力扣-62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器…

【数据结构】排序

插入排序 把当前遍历到的元素前的元素序列是排好序的,把当前元素放到前边的序列中进行排序。 直接插入排序 不带哨兵 void InsertSort(int A[],int n) { int i,j,temp; for(i1;i<n;i) if(A[i]<A[i-1]) { tempA[i]; for(ji-1;j>0 && A[j]>temp;--j) A[j…

深入理解深度学习——BERT派生模型:参数共享ALBERT

分类目录&#xff1a;《深入理解深度学习》总目录 预训练语言模型的一个趋势是使用更大的模型配合更多的数据&#xff0c;以达到“大力出奇迹”的效果。随着模型规模的持续增大&#xff0c;单块GPU已经无法容纳整个预训练语言模型。为了解决这个问题&#xff0c;谷歌提出了ALBE…

深度学习训练营之文本分类识别

深度学习训练营之文本分类识别 原文链接环境介绍前置工作设置环境设置GPU加载数据 构建词典生成数据批次和迭代器模型定义定义实例 定义训练函数和评估函数模型训练模型评估 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考…

面具下的flag

打开文件是你的名字动漫的一张图片 用010打开文件&#xff0c;比较可疑的有三处 首先文件头是正确的&#xff0c;只是这边显示的Exif&#xff0c;之前没有特别注意jpg文件有这个头 其次是文件中包含两处flag信息&#xff0c;一个是类似隐藏的文件&#xff0c;一个疑似是第二段f…