C++:探索AVL树旋转的奥秘

在这里插入图片描述

文章目录

  • 前言 AVL树为什么要旋转?
  • 一、插入一个值的大概过程
    • 1. 插入一个值的大致过程
    • 2. 平衡因子更新原则
    • 3. 旋转处理的目的
  • 二、左单旋
    • 1. 左单旋旋转方式总处理图
    • 2. 左单旋具体会遇到的情况
    • 3. 左单旋代码总结
  • 三、右单旋
    • 1. 右单旋旋转方式总处理图
    • 2. 右单旋具体会遇到的情况
    • 3. 右单旋代码总结
  • 四、双旋
    • 1. 右左双旋旋转逻辑
    • 2. 右左双旋可能会遇到的问题辨析
    • 3. 右左双旋平衡因子的处理
    • 4. 右左双旋代码总结
  • 五、左右双旋
  • 总结


前言 AVL树为什么要旋转?

AVL树需要旋转是为了保持平衡。当插入或删除节点后,某些子树的高度差超过1,就会破坏AVL树的平衡性。为了让树重新平衡,避免一边过高、一边过低的情况,旋转可以调整节点的位置,使树保持左右高度差不超过1。这样做的目的是确保查找、插入、删除操作的效率始终保持在 O(log₂ n)。简单来说,旋转就是“调位置,让树站得更稳”。


一、插入一个值的大概过程

1. 插入一个值的大致过程

  1. 按照二叉搜索树规则插入
    插入的新节点位置依据二叉搜索树的性质确定,即小于当前节点放左子树,大于当前节点放右子树。

  2. 更新平衡因子
    新增节点后,只会影响其祖先节点的高度,可能导致部分祖先节点的平衡因子发生变化。从新增节点向根节点逐步更新平衡因子。如果在更新过程中某节点的平衡因子变为2或-2,说明该节点的子树已经不平衡,需要旋转处理;否则,插入结束。

  3. 检查平衡并调整

    • 如果更新平衡因子的过程中没有发现问题(平衡因子均为0、1或-1),插入操作完成。
    • 如果出现不平衡,则对不平衡的子树进行旋转处理。旋转不仅恢复子树的平衡,还会降低子树的高度,确保不再影响其父节点的平衡因子,从而结束插入过程。

2. 平衡因子更新原则

  1. 平衡因子公式
    在这里插入图片描述

    只有子树高度发生变化时,才会影响当前节点的平衡因子。

  2. 更新规则

    • 若新增节点在父节点的右子树,则父节点的平衡因子增加1(+1)。
    • 若新增节点在父节点的左子树,则父节点的平衡因子减少1(-1)。
  3. 更新停止条件

    • 平衡因子变为0
      父节点平衡因子从 -1 变为 0 或从 1 变为 0,说明新增节点插入到高度较低的一侧,子树高度未改变,更新过程可以停止。
    • 平衡因子变为1或-1
      父节点平衡因子从 0 变为 1 或从 0 变为 -1,说明新增节点插入后子树高度增加,但仍符合平衡要求,需继续向上更新。
    • 平衡因子变为2或-2
      父节点平衡因子从 1 变为 2 或从 -1 变为 -2,说明子树高度过高,已失去平衡,需要进行旋转处理。

在这里插入图片描述

在这里插入图片描述


3. 旋转处理的目的

  1. 恢复平衡
    通过单旋转或双旋转调整节点位置,使当前子树符合平衡要求。
  2. 降低子树高度
    旋转后,子树高度恢复到插入前的水平,确保不会对更高层节点产生影响,插入过程结束。

二、左单旋

形成条件:parent->_bf == 2 && cur->_bf == 1

1. 左单旋旋转方式总处理图

  1. 失衡检测

    • 当插入的新节点导致父节点的平衡因子为 2,并且新节点被插入到右子树的右侧时,发生右右失衡(RR失衡)。
  2. 旋转操作

    • 左单旋的核心目标是将父节点的右子树(即失衡节点的右子树根)提升为新的根节点,并将原来的父节点挂接到新根节点的左子树上。
parent->right = cur->left;  // 将右子树的左子树挂接到父节点的右子树
cur->left = parent;         // 将父节点挂接为右子树的左子树
  1. 处理父节点链接问题

    • 需要确保旋转后父节点的父节点(如果存在)正确地连接到新的根节点。
      • 如果原父节点有父节点(即不是根节点),则要更新父节点的左右子树指向新的根节点。
      • 如果原父节点是根节点,则更新树的根节点。
  2. 更新平衡因子

    • 旋转后,原父节点和新的根节点的平衡因子都应设置为 0,因为旋转使得这两颗子树已经平衡。
  3. 旋转结束

    • 完成旋转后,新的根节点成为子树的根,树恢复平衡。
      在这里插入图片描述

2. 左单旋具体会遇到的情况

我们具体会遇到比如 h = 0, h = 1, h = 2 …无穷多种情况:

分析如下:

在这里插入图片描述


3. 左单旋代码总结

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

	// 重新链接
	parent->_right = curleft;
	if (curleft) // 如果curleft存在
	{
		curleft->_parent = parent;
	}

	cur->_left = parent;

	Node* ppnode = parent->_parent;
	parent->_parent = cur;

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

		cur->_parent = ppnode;
	}

	// 更改平衡因子
	parent->_bf = cur->_bf = 0;
}

三、右单旋

形成条件:parent->_bf == -2 && cur->_bf == -1

1. 右单旋旋转方式总处理图

右单旋处理的方式与左单旋是一致的,只不过是反过来了,就不多介绍了。

  1. 失衡检测

    • 当插入的新节点导致父节点的平衡因子为 -2,并且新节点被插入到左子树的左侧时,发生左左失衡(LL失衡)。
  2. 旋转操作

    • 右单旋的核心目标是将父节点的左子树(即失衡节点的左子树根)提升为新的根节点,并将原来的父节点挂接到新根节点的右子树上。
parent->left = cur->right;  // 将左子树的右子树挂接到父节点的左子树
cur->right = parent;        // 将父节点挂接为左子树的右子树
  1. 处理父节点链接问题

    • 需要确保旋转后父节点的父节点(如果存在)正确地连接到新的根节点。
      • 如果原父节点有父节点(即不是根节点),则要更新父节点的左右子树指向新的根节点。
      • 如果原父节点是根节点,则更新树的根节点。
  2. 更新平衡因子

    • 旋转后,原父节点和新的根节点的平衡因子都应设置为 0,因为旋转使得这两颗子树已经平衡。
  3. 旋转结束

    • 完成旋转后,新的根节点成为子树的根,树恢复平衡。

在这里插入图片描述


2. 右单旋具体会遇到的情况

在这里插入图片描述


3. 右单旋代码总结

// 右单旋
void RotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;

	parent->_left = curright;

	if (curright)
	{
		curright->_parent = parent;
	}

	cur->_right = parent;

	Node* ppnode = parent->_parent;
	if (ppnode == nullptr)
	{
		cur->_parent = nullptr;
		_root = cur;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else
		{
			ppnode->_right = cur;
		}
		cur->_parent = ppnode;
	}

	// 改变平衡因子
	parent->_bf = cur->_bf =  0;
}

四、双旋

1. 右左双旋旋转逻辑

形成条件:parent->_bf == 2 && cur->_bf == -1

这里是右左双旋的处理方式:

  1. 插入新节点
  2. 以cur进行右单旋
  3. 以parent进行左单旋

在这里插入图片描述


2. 右左双旋可能会遇到的问题辨析

h = 0 的情况:
在这里插入图片描述

h = 1 的情况:
在这里插入图片描述

h = 2 的情况:
在这里插入图片描述


3. 右左双旋平衡因子的处理

右左双旋的平衡因子与前面的单旋的平衡因子处理方式不同,单旋平衡因子再旋转过后全都是0,但是双旋的平衡因子不一样。

它分为以下三种情况:

  1. h = 0 的情况,及curleft._bf = 0

结果——>parent._bf = 0,cur._bf = 0,curleft._bf = 0

在这里插入图片描述


  1. h > 0 的情况下,及curleft._bf == 1
    以以下C位置插入引发的旋转。

结果: parent._bf = -1,cur._bf = 0,curleft._bf = 0

在这里插入图片描述


  1. h > 0 的情况下,及curleft._bf == -1
    以以下B位置插入引发的旋转。

结果: parent._bf = 0,cur._bf = 1,curleft._bf = 0

在这里插入图片描述


4. 右左双旋代码总结

// 右左双旋
void RotateRL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;
	int bf = curleft->_bf;

	// 右旋
	RotateR(cur);
	// 左旋
	RotateL(parent);

	// 调整平衡因子
	if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curleft->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		cur->_bf = 0;
		curleft->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		cur->_bf = 1;
		curleft->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

五、左右双旋

形成条件:parent->_bf == -2 && cur->_bf == 1

左右双旋与右左双旋类型,就是反过来的过程~

在这里插入图片描述

在这里插入图片描述

// 左右双旋
void RotateLR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;
	int bf = curright->_bf;

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

	if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curright->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		cur->_bf = 0;
		curright->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		cur->_bf = -1;
		curright->_bf = 0;
	}
}

总结

那么,到这里就结束啦!

通过学习AVL树的旋转机制,我们不仅掌握了数据结构平衡的重要性,更感受到了算法的巧妙与严谨。

如果对您有帮助的话,麻烦点一个一键三连,谢谢啦~😘😘😘😘

在这里插入图片描述

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

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

相关文章

嵌入式硬件实战基础篇(三)-四层板PCB设计-步进电机驱动(TMC2208/TMC2209)

引言:我们在嵌入式硬件杂谈(三)中有提到阻抗匹配的问题,也引入了高速PCB设计的思想,并且此篇实战基础篇主要是基础的四层板的绘制设计,后续实战会对高速板展开,本篇主要是提升读者的设计PCB板的…

数据库基础(MySQL)

1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了,为什么还要弄个数据库? 文件保存数据有以下几个缺点: 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质: 磁盘内存 为…

【C++】踏上C++学习之旅(九):深入“类和对象“世界,掌握编程的黄金法则(四)(包含四大默认成员函数的练习以及const对象)

文章目录 前言1. 实现Date类的构造函数2. 实现Date类的拷贝构造函数3. 实现Date类的赋值运算符重载4. 实现各Date对象之间的比较接口5. 实现Date对象的加减接口6. const成员7. 取地址及const取地址操作符重载 前言 在我们前面学习到了"类和对象"的四大默认成员函数(…

如何在 Elasticsearch 中配置 SSL / TLS ?

Elasticsearch 是一种流行的开源搜索和分析引擎。它被广泛用于日志或活动数据分析,全文搜索和复杂查询。但是,没有适当的安全措施,敏感数据可能很容易受到影响拦截和未经授权的访问。在 Elasticsearch 中启用 SSL/TLS 是保护数据的关键步骤。…

python之sklearn--鸢尾花数据集之数据降维(PCA主成分分析)

python之sklearn–鸢尾花数据集之数据降维(PCA主成分分析) sklearn库:Scikit - learn(sklearn)是一个用于机器学习的开源 Python 库。它建立在 NumPy、SciPy 和 matplotlib 等其他科学计算库之上,为机器学习的常见任务提供了简单…

音视频pts/dts

现在的视频流有两个非常重要的时间戳,pts和dts,其中pts是显示的时候用,dts在解码的时候用。 pts很好理解,按照pts的顺序以及duration不间断的display就可以了。 dts在解码的时候用,那么这句话怎么理解,解…

数据集-目标检测系列- 人与猫互动 猫 检测数据集 cat in the house >> DataBall

数据集-目标检测系列- 人与猫互动 猫 检测数据集 cat in the house >> DataBall DataBall 助力快速掌握数据集的信息和使用方式,会员享有 百种数据集,持续增加中。 贵在坚持! 数据样例项目地址: * 相关项目 1&#xff…

ReactPress:基于pnpm的Mono Repository方案介绍

ReactPress Github项目地址:https://github.com/fecommunity/reactpress 欢迎Star。 ReactPress基于pnpm的Mono Repository方案介绍 ReactPress是一个使用React和Node.js构建的开源发布平台,它允许用户在支持React和MySQL数据库的服务器上设置自己的博客…

stm32如何接收舵机的控制信号(而不是控制舵机)

看到很多如何stm32用pwm信号控制舵机的文章,老生常谈了 我来写一个stm32接收pwm信号的例子 ,这个pwm信号是用来控制舵机的 背景: 我需要接收航模接收机的,用来控制舵机的pwm信号, 得到这个信号后,做其他事情. 初版代码 pwm.h#ifndef _pwm_H #define _pwm_H#include "s…

Spring Boot 3.x + OAuth 2.0:构建认证授权服务与资源服务器

Spring Boot 3.x OAuth 2.0:构建认证授权服务与资源服务器 前言 随着Spring Boot 3的发布,我们迎来了许多新特性和改进,其中包括对Spring Security和OAuth 2.0的更好支持。本文将详细介绍如何在Spring Boot 3.x版本中集成OAuth 2.0&#xf…

Photoshop(PS)——人像磨皮

1.新建一个文件,背景为白色,将图片素材放入文件中 2.利用CtrlJ 复制两个图层出来,选择第一个拷贝图层,选择滤镜---杂色---蒙尘与划痕 3.调整一下数值,大概能够模糊痘印痘坑,点击确定。 4.然后选择拷贝2图层…

Vue3 + Vite 项目引入 Typescript

文章目录 一、TypeScript简介二、TypeScript 开发环境搭建三、编译方式1. 自动编译单个文件2. 自动编译整个项目 四、配置文件1. compilerOptions基本选项严格模式相关选项(启用 strict 后自动包含这些)模块与导入相关选项 2. include 和 excludeinclude…

MySql 索引视图存储变量

要求 一: 学生表:Student(Sno,Sname,Ssex ,Sage, Sdept) 学号,姓名,性别,年龄,所在系 Sno为主键 课程表:Course(Cno,Cname) 课程号,课程名 Cno为主键 学生…

使用MaxKB搭建知识库问答系统并接入个人网站(halo)

首发地址(欢迎大家访问):使用MaxKB搭建知识库问答系统并接入个人网站 前言 从OpenAI推出ChatGPT到现在,大模型已经渗透到各行各业,大模型也逐渐趋于平民化;从最开始对其理解、生成、强大的知识积累的惊叹&…

除了电商平台,还有哪些网站适合进行数据爬取?

在数字化时代,数据的价值日益凸显,而网络爬虫技术成为获取数据的重要手段。除了电商平台,还有许多其他类型的网站适合进行数据爬取,以支持市场研究、数据分析、内容聚合等多种应用场景。本文将探讨除了电商平台外,还有…

Linux-第2集-打包压缩 zip、tar WindowsLinux互传

欢迎来到Linux第2集,这一集我会非常详细的说明如何在Linux上进行打包压缩操作,以及解压解包 还有最最重要的压缩包的网络传输 毕竟打包压缩不是目的,把文件最终传到指定位置才是目的 由于打包压缩分开讲没有意义,并且它们俩本来…

tcp 超时计时器

在 TCP(传输控制协议)中有以下四种重要的计时器: 重传计时器(Retransmission Timer) 作用:用于处理数据包丢失的情况。当发送方发送一个数据段后,就会启动重传计时器。如果在计时器超时之前没有…

go环境搭建

华子目录 下载vscode安装vscodego编译器下载go编译器安装配置go环境变量vscode安装go插件测试 下载vscode 官方:https://code.visualstudio.com/Download 安装vscode vscod安装成功 go编译器下载 官方:https://golang.google.cn/ 点击下载 go编译器安…

Minikube 上安装 Argo Workflow

文章目录 步骤 1:启动 Minikube 集群步骤 2:安装Argo Workflow步骤 3:访问UI创建流水线任务参考 前提条件: Minikube:确保你已经安装并启动了 Minikube。 kubectl:确保你已经安装并配置了 kubectl&#xff…

Stable Diffusion核心网络结构——CLIP Text Encoder

🌺系列文章推荐🌺 扩散模型系列文章正在持续的更新,更新节奏如下,先更新SD模型讲解,再更新相关的微调方法文章,敬请期待!!!(本文及其之前的文章均已更新&…