数据结构——AVL树

news2025/1/22 7:52:45

AVL树

  • 概念
  • 节点定义
  • 插入
  • 旋转
    • 左单旋与右单旋
    • 双旋转
  • 验证AVL树
  • 删除(了解)

概念

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

这里是在每个结点加入了一个平衡因子,这个平衡因子是通过右子树和左子树的高度差算出来的。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

在这里插入图片描述
右子树高度-左子树高度=平衡因子
这棵树是平衡的,也就是说时间复杂度为logN,效率非常高。

节点定义

对于AVL树结点的定义,不仅仅多了一个平衡因子,还多了一个父节点的指针,是一个三叉链的结构。

template<class T,class V>
struct AVLTreeNode//AVL树结点
{
	AVLTreeNode(const pair<T, V>& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		,_factor(0)//刚创建的结点平衡因子是0,因为平衡因子只受子树影响
	{ }
	AVLTreeNode* _left;//左子树结点
	AVLTreeNode* _right;//右子树结点
	AVLTreeNode* _parent;//父节结点
	pair<T, V> _data;//结点的值,KV模型
	int _factor;//平衡因子
};

插入

我们这里只研究插入即可。
插入首先要按照平衡二叉树那样找到对应的位置。
然后将他们链接起来。
连接起来之后还要更新平衡因子。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里平衡因子已经是2或者-2了,没必要进行平衡因子的更新了。
在这里插入图片描述
这里就是最坏的情况了,可能需要更新到初始的根结点。
这里更改平衡因子其实就要用到parent结点了,这就是为什么需要一个_parent的指针了。

template<class T, class V>
class AVLTree//AVL树
{
	typedef AVLTreeNode<T, V> Node;
public:
	bool Insert(const pair<T, V>& x)
	{
		if (_root == nullptr)
		{
			_root = new Node(x);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data.first > x.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if(cur->_data.first < x.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(x);
		if (parent->_data.first > cur->_data.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//这里调节平衡因子
		while (parent)//如果parent为空就说明到了初始根结点
		{
			if (parent->_left == cur)
			{
				parent->_factor--;//如果是父节点的左子树插入了新节点就--
			}
			else if (parent->_right == cur)
			{
				parent->_factor++; //如果是父节点的右子树插入了新节点就++
			}
			//旋转
			if (parent->_factor == 0)//这里要判断parent结点是不是0,是0就说明不需要向上调整
			{
				break;
			}
			else if (parent->_factor == 1 || parent->_factor == -1)//这里就需要向上调整平衡因子了
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_factor == 2 || parent->_factor == -2)//这里就需要去旋转了
			{
				if (parent->_factor == 2 && cur->_factor == 1)
				{
					RotateL(parent);
				}
				else if (parent->_factor == -2 && cur->_factor == -1)
				{
					RotateR(parent);
				}
				else if (parent->_factor == 2 && cur->_factor == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_factor == -2 && cur->_factor == 1)
				{
					RotateLR(parent);
				}
				else//以防万一
				{
					assert(false);
				}
				break;
			}
			else//以防万一
			{
				assert(false);
			}
		}
		return true;
	}
private:
	void _Inorder(Node* _root)
	{
		if (_root == nullptr)
			return;
		_Inorder(_root->_left);
		cout << _root->_data.first << ":" << _root->_data.second << endl;
		_Inorder(_root->_right);
	}
	Node* _root = nullptr;//AVL树的根节点
};

旋转

旋转的目的;
1.让这棵树的左右树高度差不超过1
2.旋转之后也要保持这棵树是AVL树
3.更新调节平衡因子
4.旋转后的高度要和插入前相同

左单旋与右单旋

左单旋:
在这里插入图片描述
对于左单旋这张图针对的是很多种情况,下面我用三种情况举例子。
在这里插入图片描述
在60结点插入,无论是左边还是右边都会让30结点的平衡因子变成2,都会发生旋转。
在这里插入图片描述
无论在60结点的左子树的子树插入还是右子树的子树插入,都会影响30的平衡因子,都会引发旋转。
在这里插入图片描述
其实这三种情况我们能看出来,旋转的核心就是:
30结点变成60结点的左子树,原本60结点的左子树内容变成了30结点的右子树。
这里要注意,30不一定是根,有可能是局部的一个子树而已,所以需要储存30结点之前的结点,之后让30的父节点指向60,或者是根指向60。

那么如何实现代码呢?储存30的父节点,储存30结点的右子树和30结点的右子树的左子树。

void RotateL(Node* parent)//左单旋
{
	Node* pparent = parent->_parent;//30的父节点
	Node* subR = parent->_right;//60结点
	Node* subRL = subR->_left;//60结点的左子树

	parent->_right = subRL;//让30结点的右指针指向60结点的左子树
	if (subRL)//60的左子树如果为空就不能访问
		subRL->_parent = parent;

	subR->_left = parent;//让60结点的左指针指向30结点
	parent->_parent = subR;

	if (pparent)//让30的结点父节点指向60结点
	{
		if (pparent->_left == parent)
			pparent->_left = subR;
		else
			pparent->_right = subR;
	}
	else
	{
		_root = subR;

	}
	subR->_parent = pparent;//让60的结点与父节点链接

	parent->_factor = subR->_factor = 0;//调节平衡因子
}

右单旋:
在这里插入图片描述

void RotateR(Node* parent)//右单旋
{
	Node* pparent = parent->_parent;
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

	subL->_right = parent;
	parent->_parent = subL;

	if (pparent)
	{
		if (pparent->_left == parent)
			pparent->_left = subL;
		else
			pparent->_right = subL;
	}
	else
	{
		_root = subL;
	}
	subL->_parent = pparent;

	subL->_factor = parent->_factor = 0;
}

双旋转

右左
如果是这种情况怎么办呢?
在这里插入图片描述
上面的左单旋和右单旋已经行不通了。
其实只要先对3结点右单旋一次在对1结点左单旋一次就可以了。
在这里插入图片描述
这里难处理的不是过程,因为上面已经写过了,难处理的是平衡因子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

观察插入后的和最终结果的两个平衡因子,60结点的右子树给了90结点的左子树,60结点的左子树给了30结点的右子树。所以平衡因子也就能算出来了。

void RotateRL(Node* parent)//右左
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int fb = subRL->_factor;//判断60结点哪边长
	RotateR(parent->_right);
	RotateL(parent);
	if (fb == 1)//新插入的地方是在60结点的右
	{
		subR->_factor = 0;
		subRL->_factor = 0;
		parent->_factor = -1;
	}
	else if (fb == -1)//新插入的地方是在60结点的左
	{
		subR->_factor = 1;
		subRL->_factor = 0;
		parent->_factor = 0;
	}
	else//60结点是新插入的结点
	{
		subR->_factor = 0;
		subRL->_factor = 0;
		parent->_factor = 0;
	}
}

左右
在这里插入图片描述

void RotateLR(Node* parent)//左右
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int fb = subLR->_factor;
	RotateL(parent->_left);
	RotateR(parent);
	if (fb == 1)
	{
		subL->_factor = -1;
		subLR->_factor = 0;
		parent->_factor = 0;
	}
	else if (fb == -1)
	{
		subL->_factor = 0;
		subLR->_factor = 0;
		parent->_factor = 1;
	}
	else
	{
		subL->_factor = 0;
		subLR->_factor = 0;
		parent->_factor = 0;
	}
}

总结:

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

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋

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

验证AVL树

这里还需要加一个平衡因子的判断;

	int _Height(Node* root)//计算树的高度
	{
		if (root == nullptr)
			return 0;
		int l = _Height(root->_left);
		int r = _Height(root->_right);
		return l > r ? l + 1 : r + 1;//返回左子树和右子树最高高度
	}
	bool _IsBalanceTree(Node* root)
	{
		if (root == nullptr)//空树也是AVL树
			return true;
		int L = _Height(root->_left);
		int R = _Height(root->_right);
		int diff = R - L;//右减去左的高度
		if (diff != root->_factor || (diff > 1 || diff < -1))//检查这个结点的左右子树差是否合法
		{
			cout << root->_data.first << ":" << "平衡因子异常" << ":" << root->_factor << endl;
			return false;
		}
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);//整棵树的每个节点都要检查
	}

然后用两个测试用例试一下:

{16, 3, 7, 11, 9, 26, 18, 14, 15}
{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}

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

删除(了解)

在这里插入图片描述
其实删除就是类似于插入的流程,只不过更复杂了一些,需要逆向思维去推理。
假如删除9结点,对于8结点来说就要减减,删除左边就是加加。
这里8的结点平衡因子就是0了,这说明高度变了,所以需要继续往上调整平衡因子。
如果是删除6结点,那么也是四种旋转的方式。
如果是删除7结点,那就是替换法。

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

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

相关文章

抛弃丑陋的try-catch,优雅处理异常

随着业务逻辑变得越来越复杂&#xff0c;我们在编写代码时会遇到各种异常情况&#xff0c;这时就需要使用try-catch语句来捕获异常并进行处理。但是&#xff0c;大量的try-catch语句会让代码变得臃肿&#xff0c;不易维护&#xff0c;因此&#xff0c;我们需要一种优雅的方式来…

UDS统一诊断服务【六】访问时序参数0X83服务

文章目录 前言一、访问时序参数服务介绍二、数据格式2.1 请求报文2.2 子功能2.3 响应 三、举例 前言 本文介绍UDS统一诊断服务的访问时序参数0X83服务&#xff0c;希望能对你有所帮助 一、访问时序参数服务介绍 这个服务我目前在项目中没怎么用到过&#xff0c;先来看看ISO14…

【三十天精通Vue 3】第十三天 Vue 3 的插件详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、插件概述1.1 插件简介1.2 插件的使用1.3 插件的分类 二、…

五、vue基础-指令之v-bind

一、v-bind 前面要讲的一系列指令&#xff0c;主要是将值插入到模板内容中。 但是&#xff0c;除了内容需要动态来决定外&#xff0c;某些属性我们也希望动态来绑定。 比如动态绑定a元素的href属性&#xff1b;比如动态绑定img元素的src属性&#xff1b; 绑定属性我们使用v-…

智能电网中针对DOS和FDIA的弹性分布式EMA(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 ​智能电网是一种典型的信息物理融合系统,也是关系国民经济发展和国家安全的重大关键基础设施,其安全稳定运行至关重要。近年来…

java某百货店POS积分管理系统_积分点更新生成以及通票回收处理

百货店是生活中不可缺少的一部分&#xff0c;为了给顾客提供更方便的服务平台以及更好的服务质量&#xff0c;而设计了POS积分管理系统。百货店通过点积分的管理获得顾客更好的信誉&#xff0c;增加客户流量&#xff0c;获得更多的利益。在百货店经营的过程中&#xff0c;每天的…

软考 软件设计师上午题面向对象

面向过程和面向对象 省略面向对象可以省略过程&#xff0c;复杂事情简单化 类 类是对象的抽象&#xff0c;对象是类的实例 一般类是交通工具。特殊类是轮船飞机。因为他们是特殊的佳通工具&#xff0c;一个天上的一个海上的 对象 属性别名状态成员变量&#xff0c;方法也叫…

K_A31_002 基于STM32等单片机驱动315MHZ收发模块 串口显示

K_A31_002 基于STM32等单片机驱动315MHZ收发模块 串口显示 所有资源导航一、资源说明二、基本参数参数引脚说明 三、驱动说明原理&#xff1a;对应程序: 四、部分代码说明1、接线引脚定义1.1、STC89C52RC315MHZ收发模块1.2、STM32F103C8T6315MHZ收发模块 五、基础知识学习与相关…

通过Salesforce考试 (考证)后,如何在Trailhead上验证和维护证书?

随着Salesforce产品家族的不断壮大&#xff0c;学习者可以考的认证也在不断增多。从十几年前的几个认证&#xff0c;增长到现在的40多个认证。 在获得Salesforce认证之后&#xff0c;除了要将其放在LinkedIn和Trailblazer.me个人资料中&#xff0c;还有一种官方途径可以让其他…

靶机精讲之pwnOS1.0解法二

主机发现 基于前一解法 复现找到的漏洞文件利用文件 应该要想到如何利用ssh 构造利用语句 authorized_keys文件获取 访问免登录文件失败 敏感文件泄露库 发现敏感文件的经验&#xff08;精&#xff09; 按ctrlf搜索 .ssh 免密公钥 已经拿到公钥的数据 用公钥信息破解出私钥…

Redis入门介绍+linux安装

Redis是什么 Redis 是完全开源免费的&#xff0c;遵守BSD协议&#xff0c;是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value&#xff08;键值对&#xff09;数据库&#xff0c;并提供多种语言的…

【HTML5】HTML5 多媒体标签 ① ( audio 音频标签 | 音频标签常见属性值设置 | 音频标签默认代码设置 | 音频标签设置多种类型音频文件 )

文章目录 一、HTML5 多媒体标签二、音频标签三、音频标签代码示例 ( 默认操作 )四、音频标签代码示例 ( 插入 mp3 / ogg 两种格式的音频 ) 一、HTML5 多媒体标签 传统 HTML 开发中 , 如果想要向网页中嵌入音频和视频 , 需要 使用 Flash 浏览器插件才能实现 ; 在 HTML5 中 , 使…

“智慧金融”精细化客户服务+营销获客

在新兴金融产品与智能服务创新科技手段的不断推动下&#xff0c;我国金融行业的发展已经实现由“金融信息化”向“智慧金融”的阶级跨越。金融行业在客户服务、业务流程、业务开拓等方面已得到全方位的“数智化”提升&#xff0c;实现了“AI金融”客户服务、风控、营销获客的智…

基于禅道二开领导报表

上周开会的时候公司项目总监说感觉最近开发人员很轻松&#xff0c;工作量不饱和。支付力度不够。 做为开发负责人&#xff0c;对项目总监这个说法我肯定需要给予响应&#xff0c;不然老板也在场&#xff0c;后续项目想要加资源啥的都无法解释。 关注我的人知道&#xff0c;之前…

客流统计分析系统增强售楼处、4S店飞单管理能力

客流统计分析系统可以为售楼处和4S店提供有效的飞单管理能力&#xff0c;使其能够更好地管理客户信息和提高销售效率。首先&#xff0c;客流统计分析系统可以对售楼处和4S店的客流进行实时监控和分析。通过使用摄像头、人脸识别等技术&#xff0c;可以对进店的客户进行统计和分…

whisper技术导读2

1、数据处理 根据最近利用互联网上的网络规模文本来训练机器学习系统的趋势&#xff0c;我们采用了一种极简的方法来进行数据预处理。与语音识别方面的许多工作相比&#xff0c;我们训练Whisper模型在没有任何显著标准化的情况下预测转录本的原始文本&#xff0c;依靠序列到序列…

ffmpeg 向流媒体服务器推RTSP 流时候的 交互过程以及接收到的 RTP包解析

之前写了RTSP服务端 和客户端拉流之间的交互流程 正好最近在看流媒体服务器 中RTSP的部分 copy了下源码 编译下发现不能正常播放 借此机会 记录下rtsp推流时候和服务器交互的流程 以上是推流端向服务器推流的时候 的整个流程 之后就是媒体数据的发送了 然后在看下vlc播…

electron_笔记

创建你的第一个应用: package.json: {"name": "my-electron-app","version": "1.0.0","description": "my demo","main": "main.js","scripts": {"dev": "electr…

Mac安装和卸载node和npm

1、官网下载 访问nodejs官网&#xff0c;点击稳定版&#xff0c;并下载 https://nodejs.org/en 2、安装 双击刚下载的文件&#xff0c;按步骤默认安装就行 3、 验证 安装完成后打开终端 npm -vnode -v如下图出现版本信息&#xff0c;说明安装成功 4、环境配置 打开M…

Vue中的嵌套路由

router官网-嵌套路由 实际生活中的应用界面&#xff0c;通常由多层嵌套的组件组合而成。同样地&#xff0c;URL 中各段动态路径也按某种结构对应嵌套的各层组件&#xff0c;例如&#xff1a; <body><div id"app"><h1>欢迎使用路由导航</h1&g…