【C++进阶05】AVL树的介绍及模拟实现

在这里插入图片描述

一、AVL树的概念

二叉搜索树的缺点
二叉搜索树虽可以缩短查找效率
但如果数据有序或接近有序
二叉搜索树将退化为单支树
查找元素相当于在顺序表中搜索元素,效率低下

AVL树便是解决此问题

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

AVL树或空树
或是具有以下性质的二叉搜索树

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

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)

二、AVL树实现的基本框架

2.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;
	
	// balance factor 平衡因子
	int _bf; 

	// 构造函数
	AVLTreeNode(const pair<K, V>& kv)
		, left(nullptr)
		, right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

2.2 AVL树的基本结构

template <class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
private:
	Node* _root = nullptr; // 根节点
};

2.3 AVL树的插入

AVL树插入步骤:

  1. 按二叉搜索树的方式插入新节点
  2. 更新平衡因子
  3. 若平衡因子失衡,需要旋转处理

平衡因子失衡后的旋转处理

  1. 更新完, 平衡因子没问题(|bf| <= 1)
    平衡因子结构未受影响, 不需要处理

  2. 更新完,平衡因子有问题(|bf| > 1)
    平衡结构受影响,需要处理(旋转)

原因:

插入新增节点
会影响祖先的平衡因子(全部或部分)
当前节点平衡因子等于
右树节点个数减左树节点个数

  1. cur == parent->right 则parent->bf++
  2. cur == parent->left 则parent->bf–

parent所在子树高度发生变化
则需要继续往上更新爷爷节点
否则就不更新

parent->bf == 1 || parent->bf == -1 
// 则说明parent所在子树变了, 继续更新

插入节点更新平衡因子后分为三种情况

  1. 插入前parent->bf == 0
    说明插入前左右两边高度相等
    插入后有一边高1
    说明parent一边高,一边低,高度变了

在这里插入图片描述
2.

parent->bf == 2 || parent->bf == -2

在这里插入图片描述

则说明parent所在子树不平衡
需要处理这颗子树(旋转处理)

  1. parent->bf == 0
    parent所在子树高度不变
    不用继续往上更新,这一次插入结束
    说明插入前parent->bf == 1 or -1
    插入前一边高,一边低
    插入节点填上矮的那边,高度不变

在这里插入图片描述

三、AVL树的旋转

旋转的原则:
保持它是搜索树

旋转的目的:

  1. 让这棵子树平衡
  2. 降低这棵子树的高度

左旋过程

  1. 30的左子树25变成20的右子树
  2. 20变成30的左子树
    30变成整棵树的根

实际旋转中的节点值可能不是这些值
但也是按这些点位去旋转的
在这里插入图片描述
根据节点插入位置的不同
AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧—左左:
右单旋
图中h为子树的高度
在这里插入图片描述

2. 新节点插入较高右子树的右侧—右右:
左单旋

在这里插入图片描述
3. 新节点插入较高左子树的右侧—左右:
先左单旋再右单旋

在这里插入图片描述

将双旋变成单旋后再旋转,即:
先对30进行左单旋
然后再对90进行右单旋

图中只展示了b插入引发双旋的场景
本质有三种引发双旋的场景:

  1. 在b插入,b的高度变化+1
  2. 在c插入,c的高度变化+1
  3. 60本身就是新增节点

旋转完成后再考虑平衡因子的更新
不同场景的插入,60的平衡因子也不同
分别为-1,1,0
且每种场景的插入旋转完后90和30的
平衡因子都不一样
代码的实现通过记录60这个点位的平衡因子
旋转完后
根据不同场景的插入更新90和30的平衡因子

4. 新节点插入较高右子树的左侧—右左:
先右单旋再左单旋

在这里插入图片描述
参考先左单旋再右单旋

四、插入代码的实现

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->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	// new的节点的parent还指向空
	cur->_parent = parent;

	// 1. 更新平衡因子
	while (parent)
	{
		if (cur == parent->_right)
		{
			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) // parent->right是cur
			{
				RotateL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == -1) // parent->
			{
				RotateR(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}

			break; // 处理完,break,否则会一直循环
		}
		else
		{
			// 如果插入之前就有问题
			assert(false);
		}
	}
	
	return true;
}

五、AVL树旋转代码实现

void RotateL(Node* parent) // 左单旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL) // subRL可能为空
		subRL->_parent = parent;

	// 旋转的不一定是整棵树
	Node* pparent = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

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

		subR->_parent = pparent;
	}

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

void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

	Node* pparent = parent->_parent;

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

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

		subL->_parent = pparent;
	}

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

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

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

	subLR->_bf = 0; // subLR的左一定等于0
	if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

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

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

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

六、全部代码实现

AVL树模拟实现全部代码:gitee链接

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法】不使用库函数,求解立方根

牛客原题&#xff1a;https://www.nowcoder.com/practice/caf35ae421194a1090c22fe223357dca?tpId37&tqId21330&rp1&ru/exam/oj/ta&qru/exam/oj/ta&sourceUrl%2Fexam%2Foj%2Fta%3FtpId%3D37&difficultyundefined&judgeStatusundefined&tags&a…

【LeetCode】winter vacation training

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb; 有效的字母异位词&#x…

websocket介绍并模拟股票数据推流

Websockt概念 Websockt是一种网络通信协议&#xff0c;允许客户端和服务器双向通信。最大的特点就是允许服务器主动推送数据给客户端&#xff0c;比如股票数据在客户端实时更新&#xff0c;就能利用websocket。 Websockt和http协议一样&#xff0c;并不是设置在linux内核中&a…

monocle2 fibroblast silicosis inmt

gc() #####安装archr包##别处复制 .libPaths(c("/home/data/t040413/R/x86_64-pc-linux-gnu-library/4.2","/home/data/t040413/R/yll/usr/local/lib/R/site-library", "/usr/local/lib/R/library","/home/data/refdir/Rlib/")).libPa…

20、Kubernetes核心技术 - 基于Prometheus和Grafana搭建集群监控平台

目录 一、概述 二、监控平台架构图​编辑 三、部署 Prometheus 3.1、Prometheus简介 3.2、部署守护进程node-exporter 3.3、部署rbac 3.4、ConfigMap 3.5、Deployment 3.6、Service 3.7、验证Prometheus 四、部署Grafana 4.1、Deployment 4.2、Service 4.3、Ing…

二叉树及其实现

二叉树 一.树的概念及结构1.1树的概念1.2相关概念 2.二叉树的概念及结构2.1 概念2.2 特殊的二叉树 3.二叉树的遍历3.1 前序、中序以及后序遍历3.2 层序遍历3.3 判断二叉树是否是完全二叉树3.4 二叉树的高度3.5 二叉树的叶子节点个数3.6 二叉树的第k层的节点个数3.7 二叉树销毁3…

吃惯人血馒头的 VC 机构,是否还能在 Fair launch 的散户牛市中胜出?

“吃惯人血馒头的 VC 机构&#xff0c;在 Fair launch 革命中正在失去话语权&#xff0c;而散户、社区完全主导加密行业的时代&#xff0c;正在悄然而至。” LaunchPad 是代币面向市场的重要一环&#xff0c;将代币推向市场&#xff0c;加密项目将能够通过代币的销售从市场上募…

RK3568驱动指南|第十篇 热插拔-第114章 内核发送事件到用户空间的方法

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

java解析json复杂数据的第三种思路

文章目录 一、概述二、数据预览1. 接口json数据2. json转xml数据 三、代码实现1. pom.xml2. 核心代码3. 运行结果 四、源码传送 一、概述 接上篇 java解析json复杂数据的两种思路 我们已经通过解析返回json字符串得到数据,现在改变思路, 按照如下流程获取数据: #mermaid-svg-k…

【数据库原理】(11)SQL数据查询功能

基本格式 SELECT [ALL|DISTINCT]<目标列表达式>[,目标列表达式>]... FROM <表名或视图名>[,<表名或视图名>] ... [ WHERE <条件表达式>] [GROUP BY<列名 1>[HAVING <条件表达式>]] [ORDER BY <列名 2>[ASC DESC]];SELECT: 指定要…

springboot医院信管系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

FastDFS之快速入门、上手

知识概念 分布式文件系统 通过计算机网络将各个物理存储资源连接起来。通过分布式文件系统&#xff0c;将网络上任意资源以逻辑上的树形结构展现&#xff0c;让用户访问网络上的共享文件更见简便。 文件存储的变迁&#xff1a; 直连存储&#xff1a;直接连接与存储&#xf…

Oracle regexp_substr

select regexp_substr(123|456|789, [^|], 1, 2) from dual;

暴雨信息发布算力网络应用平台打造零感知算网服务新模式

为进一步优化算力网络应用服务能力和降低算力网络使用难度&#xff0c;暴雨信息突破基于算力网络的实例跨域协同与迁移、基于测试评估的应用度量和解构等技术&#xff0c;研发并推出算力网络应用平台。该系统通过提供一种即开即用、按需付费的零感知算网应用服务&#xff0c;使…

Python基础语法(上)——基本语法、顺序语句、判断语句、循环语句(有C++基础快速掌握Python语言)

文章目录 0.python小技巧与易错点1.python 与 c 语法有哪些区别2.Python基本语法2.1python的变量类型2.2python中的运算符2.3python中的表达式2.4python中的输入输出 3.python判断语句3.1基本用法&#xff1a;3.2关于else if 的用法3.3关于pass语句3.4python变量的作用域3.5pyt…

THB6128两相四线步进电机PWM驱动控制

THB6128两相四线步进电机驱动控制模块&#xff0c;可以驱动57及以下两相四线步进电机。该模块有以下优点&#xff1a; 芯片使用双全桥MOSFET驱动&#xff0c;低导通电阻Ron 0.55Ω最高耐压36V&#xff0c;峰值电流2.2A&#xff0c;持续电流2A&#xff0c;电流设定通过拨码开关…

大模型LLM在 Text2SQL 上的应用实践

一、前言 目前&#xff0c;大模型的一个热门应用方向Text2SQL&#xff0c;它可以帮助用户快速生成想要查询的SQL语句&#xff0c;再结合可视化技术可以降低使用数据的门槛&#xff0c;更便捷的支持决策。本文将从以下四个方面介绍LLM在Text2SQL应用上的基础实践。 Text2SQL概…

常用注解/代码解释(仅个人使用)

目录 第一章、代码解释①trim() 方法以及(Arrays.asList(str.split(reg)));②查询字典项②构建后端镜像shell命令解释 第二章、注解解释①PropertySource注解与Configurationproperties注解的区别 第三章、小知识①Linux系统中使用$符号表示变量 友情提醒: 先看文章目录&#…

Android学习(四):常用布局

Android学习&#xff08;四&#xff09;&#xff1a;常用布局 五种常用布局 线性布局&#xff1a;以水平或垂直方向排列相对布局&#xff1a;通过相对定位排列帧布局&#xff1a;开辟空白区域&#xff0c;帧里的控件(层)叠加表格布局&#xff1a;表格形式排列绝对布局&#x…

C语言之三子棋小游戏的应用

文章目录 前言一、前期准备模块化设计 二、框架搭建三、游戏实现打印棋盘代码优化玩家下棋电脑下棋判断输赢 四、结束 前言 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#…