【C++】AVL树的插入实现

news2024/11/19 19:35:56

目录

  • 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
	{}
};
template <class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv);
private:
	Node* _root = nullptr;
};

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/681008.html

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

相关文章

【群智能算法】基于浣熊优化算法的工程应用问题优化【Matlab代码#43】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】1. 原始COA算法1.1 开发阶段1.2 探索阶段 2. 工程应用问题优化2.1 压力容器设计2.2 拉压弹簧设计 3. 部分代码展示4. 仿真结果展示5. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章…

Spring事件机制让你的代码更优雅

今天为大家分享一下Spring的事件机制的使用&#xff0c;它是spring中一个非常好用也很实用的机制。 1. spring事件机制的概念 Spring的事件机制是基于观察者模式实现的&#xff0c;它可以在我们的实际应用程序中实现代码之间的解耦&#xff0c;提高代码的可维护性和可扩展性。…

ShardingSphere-Proxy 分库分表

安装ShardingSphere-Proxy 中间件封装 定位为透明化的数据库代理端&#xff0c;提供封装了数据库二进制协议的服务端版本&#xff0c;用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL版本&#xff0c;它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端&#x…

面试必问:四种经典限流算法

今天给大家分享一下限流方面的&#xff0c;大家都知道&#xff0c;在分布式系统中&#xff0c;高并发场景下&#xff0c;为了防止系统因突然的流量激增而导致的崩溃&#xff0c;同时保证服务的高可用性和稳定性&#xff0c;限流是最常用的手段。希望能够给大家带来帮助&#xf…

STM32常见面试题

一、STM32F1和F4的区别&#xff1f; 内核不同&#xff1a;F1是Cortex-M3内核&#xff0c;F4是Cortex-M4内核&#xff1b; 主频不同&#xff1a;F1主频72MHz&#xff0c;F4主频168MHz&#xff1b; 浮点运算&#xff1a;F1无浮点运算单位&#xff0c;F4有&#xff1b; 功能性能&…

【无标题】vue中表单绑定v-model

表单绑定v-model 表单控件在实际开发中是非常常见的。特别是对于用户信息的提交&#xff0c;需要大量的表单。 Vue中使用v-model指令来实现表单元素和数据的双向绑定。 案例的解析&#xff1a; 当我们在输入框输入内容时 因为input中的v-model绑定了message&#xff0c;所以会…

Vue-搭建Vuex开发环境

1 安装Vuex 安装之前需要了解一个版本问题&#xff0c;在vue2中&#xff0c;要用vuex的3版本&#xff0c;在vue3中&#xff0c;要用vuex的4版本&#xff0c;要严格遵循这个版本要求&#xff0c;不然就会出现各种意想不到的问题&#xff0c;例如下方安装报错&#xff0c;就算因…

ubuntu修改应用图表|任务栏收藏|快捷方式|收藏夹

需要知道应用程序对应的.desktop文件的位置&#xff0c;然后使用sudo gedit打开。修改对应位置的信息就可以了。 参考&#xff1a;Linux下Desktop文件入门 1.desktop文件位置 一般存放在/usr/share/applications这个位置里面。 以vscode为例&#xff0c;使用sudo gedit code…

POJ - 2287 Tian Ji -- The Horse Racing

题目来源 2287 -- Tian Ji -- The Horse Racing (poj.org) 题目描述 田忌赛马是中国历史上一个著名的故事。 这个故事发生在2300年前&#xff0c;田忌是齐国的一个大官&#xff0c;他喜欢和齐王以及其他公子赛马。 田忌和齐王都有三类马&#xff0c;分别是下等马&#xff0…

1750_使用gcc对嵌入式代码控制逻辑进行测试

全部学习汇总&#xff1a; GreyZhang/c_basic: little bits of c. (github.com) 相信很多人的C语言学习是从printf开始的&#xff0c;为了验证我们的程序代码运行结果&#xff0c;我们通常会选择使用printf打印出我们计算的结果看一下是否与预期一致。到了嵌入式软件开发&#…

web前端工程师个人简历编写(附详细代码)

web前端工程师 h5css3完成简历编写&#xff0c;效果如下&#xff1a; 底部附有详细代码编写 编写Web前端工程师个人简历时&#xff0c;需要注意以下几点&#xff1a; 简洁明了&#xff1a;简历应该简洁明了&#xff0c;内容要点突出&#xff0c;避免冗长和废话。用简洁的语言…

Boost的介绍、安装与环境配置

文章目录 一、Boost库简介二、Boost的安装与编译&#xff08;一&#xff09;下载解压&#xff08;二&#xff09;编译静态库 三、配置VS环境四、其它环境的配置&#xff08;vscode、DevC&#xff09;&#xff08;一&#xff09;在DEVC中配置使用boost库的环境&#xff08;二&am…

java: 程序包javax.servlet.http不存在

问题描述 当项目从2.7.x的springboot升级到3.0.x的时候&#xff0c;遇到一个问题“java: 程序包javax.servlet.http不存在” 。这可能是一些包的精简变化导致的。错误信息如下&#xff1a; 错误代码段 package com.softdev.system.generator.config;import com.softdev.system…

深度学习-ubuntu18.04+RTX3080+cuda11.2+cudnn8.1.0下安装polarstream全纪录

&#xff11;、安装 创建一个python3.7的虚拟环境 conda create --name polarstream python3.7 激活虚拟环境 source activate polarstream以下操作均在虚拟环境中进行 安装与cuda和python版本对应的torch版本,参考https://blog.csdn.net/didadifish/article/details/12748…

【软件设计师暴击考点】操作系统知识高频考点暴击系列【二】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

栈的应用——括号匹配、表达式求值、递归

目录 一、栈在括号匹配中的应用逻辑实现代码实现 二、栈在表达式求值中的应用手算实现代码实现 三、栈在递归中的应用逻辑实现代码实现 一、栈在括号匹配中的应用 括号匹配&#xff0c;顾名思义。若括号按照正确的格式嵌套&#xff0c;则可正确匹配&#xff0c;例如([])&#…

scratch lenet(12): LeNet-5输出层和损失函数的计算

文章目录 1. 目的2. 输出层结构2.1 Gaussian Connection2.2 Gaussian Connection 的 weight 可视化 3. Loss Function3.1 当前类别判断错误时&#xff0c;loss function 中的项&#xff08;基本项&#xff09;3.2 判断为其他类别时&#xff0c; loss function 中的项&#xff0…

Spring发展历程及其体系结构

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Spring 目录 发展历程体系结构 发展历程 体系结构 Spring框架的体系结构的主要组成部分&#xff1a; 核心容器…

idea如何集成Tomcat

&#xff08;1&#xff09;、这里应该找Add Configuration点击这里&#xff1a;如果没有标志&#xff0c;点击Exit (2)、这里可以配置一个配置项&#xff1a; &#xff08;3&#xff09;、loacl是本地&#xff0c;那个是远程&#xff1a;这里我选择本地 &#xff08;4&#xff…

代码随想录算法训练营第四十二天 | 01背包理论基础,01背包理论基础(滚动数组),416. 分割等和子集

代码随想录算法训练营第四十二天 | 01背包理论基础&#xff0c;01背包理论基础&#xff08;滚动数组&#xff09;&#xff0c;416. 分割等和子集 1.1 01背包理论基础 01背包 回溯法&#xff1a;暴力的解法是o(2^n)指数级别的时间复杂度&#xff0c;需要动态规划的解法来进行优…