【平衡二叉树】AVL树(右单旋和左单旋的情况)

news2024/11/18 13:29:55
图片名称

🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | 初阶数据结构 | Linux


在这里插入图片描述

文章目录

  • `1. AVL树的定义`
  • `2. C++实现AVL树`
    • 2.1 插入——左左型的右旋
    • 2.2 插入——右右型的左旋
  • `3. 总结`


1. AVL树的定义


二叉搜索树(BST)是一个节点一个节点进行插入的,当插入的数据是有序的时候,二叉搜索树会退化成类似于单链表的形式,此时二叉搜索树的查询效率会无限接近O(N),类似于下图,然而我们期待的是最多查找次数为高度次,即时间复杂度是O(logN)

在这里插入图片描述

AVL树是什么意思呢?

两位俄罗斯的数学家 G.M.Adelson-VelskiiE.M.Landis 在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

因此AVL树本质上是一棵二叉搜索树,但是具有以下性质:

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

👨🏻‍💻平衡因子简介

平衡因子(bf),英文 balance factor,即左右子树高度之差。

  • 平衡因子 = 右子树高度 - 左子树高度

在AVL树中,必须要保证 -1 <= bf <= 1

👨🏻‍💻计算平衡因子

下图的二叉树,计算每个节点的平衡因子,图示如下:(左图为左右子树的高度的分析,右图为平衡因子)
在这里插入图片描述

👨🏻‍💻AVL树的作用

我们已经了解到,AVL树的主要作用就是优化二叉搜索树当插入数据接近有序的时候查找的时间复杂度,从O(N) -> O(logN),例如当我们插如 1,2,3,4,5的时候 :

在这里插入图片描述


2. C++实现AVL树


👨🏻‍💻AVL树节点的定义

在每个节点中,应该有左右孩子的指针,指向父亲的指针(更新平衡因子时有用),平衡因子_bf,以及keyval(KV模型),用pair封装,代码如下:

template<class K, class V>
struct AVLTNode
{
	typedef AVLTNode<K, V> Node;
	Node* _left;
	Node* _right;
	Node* _parent;
	pair<K, V> _kv;
	int _bf;

	AVLTNode(const pair& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_bf(0)
	{}
};

👨🏻‍💻AVL树节点的插入

插入的时候,大体思路还是和二叉搜索树类似,只是要更新平衡因子,怎么更新呢?插入一个节点,要更新所有它的父节点(有直系关系的节点,一直到根节点),如果插入的节点是它的右子树,平衡因子就加加,插入的节点是左子树,平衡因子就减减,那么平衡因子就会有以下几种情况:

  1. _bf == 0,说明插入之前的_bf == 1 || _bf == -1,满足AVL树,不需要进行调整;
  2. _bf == 1 || _bf == -1,说明插入之前的_bf == 0 ,说明父节点的高度增加了,要继续往上更新;
  3. _bf == 2 || _bf == -2,不满足AVL树,要进行旋转调整,怎么旋转调整呢?待会再细说。
    • _bf 不可能大于2或者小于-2,因为当_bf == 2 的时候就会进行调整,使它的平衡因子变成0,至于为什么是0,调整的时候细说。

代码:

bool insert(const pair& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (kv.first > cur->_kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	// 更新所有它的父节点,一直到根节点
	while (parent)
	{
		if (cur == parent->_left)
		{
			--parent->_bf;
		}
		else if (cur == parent->_right)
		{
			++parent->_bf;
		}
		// 平衡因子为0,说明插入之前parent的平衡因子为1或者-1,高度不变,不需要调整
		if (parent->_bf == 0)
		{
			break;
		}
		// 平衡因子为正负1,说明插入之前的平衡因子为0,高度增加,要继续往上调整
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = cur->_parent;
		}
		// 平衡因子为正负2,要进行旋转调整
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			// 这里待会再实现
		}
		else
		{
			assert(false);
		}
			
	}
		
}

2.1 插入——左左型的右旋


👨🏻‍💻出现情况
在这里插入图片描述
由图可见,插入之前满足AVL树的条件,插入之后根节点T的左右子树高度之差不再满足绝对值小于等于1,破坏了AVL树的规则,要对其进行旋转。

T是平衡因子大于1的节点,L是它的左节点,Y是L的右节点

在节点T的 左节点左子树 上插入了一个节点,这种称之为 左左型,要进行右旋转。

👨🏻‍💻右旋具体步骤

  1. T向右旋转成为L的右子树;
  2. L的右子树成为T的左子树。

:为什么可以这样旋转呢?

我们可以发现这样的一个大小关系:L < Y < T,因此我们发现旋转过后还是满足二叉搜索树的规则。

👨🏻‍💻图解如下:

在这里插入图片描述

👨🏻‍💻右旋动画演示
在这里插入图片描述
👨🏻‍💻右旋代码实现

这里看样子只需要改两个指针,但其实这两个指针牵扯到的其他指针也是很多的:

  1. 当把L的右孩子(Y)给T的时候也要改变Y的父指针,但是指向Y的指针可能为空,所以要特殊判断一下;
  2. 当L的右指针指向T的时候,要改变T的父指针,T可能本身就是根节点,那此时就要把L设置为根节点,并把T的父指针指向L;如果T不是根节点,就要记录T的父节点ppNode,并把L的父指针指向ppNode,把T的父指针指向L;
  3. 改变parent和L的平衡因子。
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	// 考虑为空的情况
	if (subLR)
		subLR->_parent = parent;
	subL->_right = parent;
	// 记录此时父亲的父节点的指向
	Node* ppNode = parent->_parent;
	parent->_parent = subL;
	// 如果父节点为根节点
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	// 父节点不是根节点
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
			subL->_parent = ppNode;
		}
		else
		{
			ppNode->_right = subL;
			subL->_parent = ppNode;
		}
	}
	// 更新平衡因子
	parent->_bf = subL->_parent = 0;
}

2.2 插入——右右型的左旋


👨🏻‍💻出现情况
在这里插入图片描述
由图可见,插入之前满足AVL树的条件,插入之后根节点T的左右子树高度之差不再满足绝对值小于等于1,破坏了AVL树的规则,要对其进行旋转。

T是平衡因子大于1的节点,R是它的右节点,X是R的左节点

在节点T的 右节点右子树 上插入了一个节点,这种称之为 右右型,要进行左旋转。

👨🏻‍💻左旋具体步骤

  1. T向左旋转称为R的左子树;
  2. R的左子树成为T的右子树。

:为什么可以这样旋转呢?

我们可以发现这样的一个大小关系:T < X < R,因此我们发现旋转过后还是满足二叉搜索树的规则。

👨🏻‍💻图解如下:

在这里插入图片描述
👨🏻‍💻左旋动画演示
在这里插入图片描述
👨🏻‍💻左旋代码实现

这里要注意的点和右旋类似:

  1. 当把R的左孩子X给T的时候,要改变R的父指针的指向,但是X可能为空,所以要特殊判断一下;
  2. 当R的左孩子指向T的时候,要改变T的父指针的指向,T有可能是根节点,其父指针就是空,要把R设置为根节点,并改变T的父指针指向R;T如果不是根节点,此时就要记录其父节点ppNode,并把R的父指针指向ppNode,改变T的父指针指向R;
  3. 改变R和parent的平衡因子。
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;
			subR->_parent = ppNode;
		}
		else
		{
			ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}
	subR->_bf = parent->_bf = 0;
}

3. 总结

插入位置状态
在结点T的左结点(L)的 左子树(L) 上做了插入元素左左型,右旋
在结点T的右结点(R)的 右子树(R) 上做了插入元素右右型,左旋

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

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

相关文章

本地部署大模型ollama+docker+open WebUI/Lobe Chat

文章目录 大模型工具Ollama下载安装运行Spring Ai 代码测试加依赖配置写代码 ollama的web&Desktop搭建部署Open WebUI有两种方式Docker DesktopDocker部署Open WebUIDocker部署Lobe Chat可以配置OpenAI的key也可以配置ollama 大模型的选择 本篇基于windows环境下配置 大模型…

【多变量控制系统 Multivariable Control System】(3)系统的状态空间模型至转换方程模型(使用Python)【新加坡南洋理工大学】

一、转换式 二、系统的状态空间模型 由矩阵A, B, C, D给出&#xff1a; 三、由状态空间模型转化为转换方程模型 函数原型&#xff08;版权所有&#xff1a;scipy&#xff09;&#xff1a; def ss2tf(A, B, C, D, input0):r"""State-space to transfer functi…

计算机毕业设计Python+Spark知识图谱高考志愿推荐系统 高考数据分析 高考可视化 高考大数据 大数据毕业设计

毕业设计&#xff08;论文&#xff09;任务书 毕业设计&#xff08;论文&#xff09;题目&#xff1a; 基于大数据的高考志愿推荐系统 设计&#xff08;论文&#xff09;的主要内容与要求&#xff1a; 主要内容&#xff1a; 高…

Unity 编辑器工具 - 资源引用查找器

在Unity项目开发过程中&#xff0c;管理和维护资源之间的引用关系是至关重要的。当然我们项目也是需要这个功能 毕竟项目大了之后查找资源引用还是交给 资源引用查找器 比较好。 功能概述 资源引用查找器允许开发者选择一个目标资源&#xff0c;并在整个项目中查找引用了该资…

STM32:GPIO输出

文章目录 1、GPIO介绍1.1 GPIO的基本结构1.1 GPIO的位结构 2、 GPIO工作模式3、GPIO标准外设库接口函数3.1 RCC接口函数3.2 GPIO接口函数3.2.1 GPIO的读取函数3.2.1 GPIO的写入函数 4、GPIO的初始化 1、GPIO介绍 GPIO&#xff08;General Purpose Input Output&#xff09;通用…

腾讯云CentOS7使用Docker安装ElasticSearch与Kibana详细教程

文章目录 一、安装ElasticSearch二、安装Kibana 一、安装ElasticSearch 使用Docker拉取ElasticSearch镜像 这里版本选择的是7.15.2 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.22. 查看ElasticSearch的镜像id docker images3. 创建ElasticSearch容器 …

Linux基础指令001

名称日期版本说明作者了解并熟练运用Linux基础指令2024/05/04v0.0.1汇总篇lgb 一&#xff0c;了解Linux,并安装 Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协…

基于OpenCv的图像金字塔

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

Finder Windows for Mac:双系统窗口,一键切换!

Finder Windows for Mac是一款专为Mac用户设计的实用工具&#xff0c;它模拟了Windows系统的窗口管理功能&#xff0c;让Mac用户也能享受到类似Windows的窗口操作体验。这款软件的主要功能是提供一个浮动面板&#xff0c;帮助用户随时即时访问打开的Finder窗口列表&#xff0c;…

jQuery Moblie 笔记14 开发跨平台移动设备网页

相关内容&#xff1a;jQuery Moblie基础、操作、移动设备仿真器、jQuery Moblie网页实例、jQuery Moblie的UI组件、…… jQuery推出了一套新的函数库jQuery Mobile&#xff0c;目的是希望能够统一当前移动设备的用户界面(UI)。 移动设备开发应用程序目前大致分为两种&#xff…

IDEA启动Tomcat启动失败:jar包未部署【部署jar包】

IDEA启动Tomcat报错java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener&#xff1a;jar包未部署【部署jar包】 学习java&#xff0c;开始跟着教程的步伐学习maven下载jar包&#xff0c;tomcat启动项目&#xff0c;发现项目未启动成功也…

NAPLISTENER>APT-REF2924组织后门扫描利用工具

项目地址:https://github.com/MartinxMax/NAPLISTENER 简介 这是与REF2924 APT组关联的Wmdtc.exe后门扫描程序。 我们可以在Windows和Linux上使用此工具来扫描目标服务器。 如果发现字段[Microsoft HTTPAPI/2.0]存在&#xff0c;您可以尝试扫描组织的后门。 当第一次运行脚本…

【Android学习】日期和时间选择对话框

实现功能 实现日期和时间选择的对话框&#xff0c;具体效果可看下图(以日期为例) 具体代码 1 日期对话框 1.1 xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android&quo…

xss漏洞简介

漏洞简介 跨站脚本&#xff08;Cross-site scripting ,简称 XSS&#xff09;是一种经常出现在Web应用程序中的计算机安全漏洞&#xff0c;是由于web应用程序对用户的输入过滤不足而产生的&#xff0c;是代码注入的一种&#xff0c;XSS就是攻击者利用网站漏洞把恶意脚本代码&am…

wpf线程中更新UI的4种方式

在wpf中&#xff0c;更新UI上面的数据&#xff0c;那是必经之路&#xff0c;搞不好&#xff0c;就是死锁&#xff0c;或者没反应&#xff0c;很多时候&#xff0c;都是嵌套的非常深导致的。但是更新UI的方式&#xff0c;有很多的种&#xff0c;不同的方式&#xff0c;表示的意思…

目标跟踪—卡尔曼滤波

目标跟踪—卡尔曼滤波 卡尔曼滤波引入 滤波是将信号中特定波段频率滤除的操作&#xff0c;是抑制和防止干扰的一项重要措施。是根据观察某一随机过程的结果&#xff0c;对另一与之有关的随机过程进行估计的概率理论与方法。 历史上最早考虑的是维纳滤波&#xff0c;后来R.E.卡…

anaconda、cuda、tensorflow、pycharm环境安装

anaconda、cuda、tensorflow、pycharm环境安装 anaconda安装 anaconda官方下载地址 本文使用的是基于python3.9的anaconda 接下来跟着步骤安装&#xff1a; 检验conda是否成功安装 安装CUDA和cuDNN 提醒&#xff0c;CUDA和cuDNN两者必须版本对应&#xff0c;否者将会出错…

SpringBoot---------Swagger

第一步&#xff1a;引入依赖 <!-- swagger--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId></dependency> 第二步&#xff1a;配置文件 import com.sky.intercept…

Educational Codeforces Round 165 (Rated for Div. 2) (C、D)

1969C - Minimizing the Sum 题意&#xff1a; 思路&#xff1a;观察到操作数很小&#xff0c;最值问题操作数很容易想到dp&#xff0c;用表示第个元素&#xff0c;操作了次的最小值总和&#xff0c;转移的时候枚举连续操作了几次即可&#xff0c;而连续操作了几次即将全部变成…

微信小程序 uniapp家庭食谱菜谱食材网上商城系统小程序ko137

随着生活节奏的不断加快&#xff0c;越来越多的人因为工作忙而没有时间自己出去订购喜欢的菜品。随着Internet的飞速发展&#xff0c;网络已经成为我们日常生活中必不可少的部分&#xff0c;越来越多的人也接受了电子商务这种快捷、方便的交易方式。网上订餐其独有的便捷性和直…