C++漫步结构与平衡的殿堂:AVL树

文章目录

  • 1.AVL树的概念
  • 2.AVL树的结构
  • 3.AVL树的插入
  • 4.AVL树的旋转
    • 4.1 左单旋
    • 4.2 右单旋
    • 4.3 右左双旋
    • 4.4 左右双旋
  • 5.AVL树的删除
  • 6.AVL树的高度
  • 7.AVL树的平衡判断
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成 O(N),因此 mapset 等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现

1.AVL树的概念

在这里插入图片描述

我们已经从多种树型结构走到现在,每一次变化都是为了提高搜索的效率,即时间复杂度

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

那么什么是AVL树呢?

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

在这里插入图片描述

一棵 AVL 树或者是空树,应该是具有以下性质的二叉搜索树:

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

二叉搜索树在理想情况下时间复杂度与二叉平衡搜索树相同,均为 O ( l o g 2 n ) O(log_2 n) O(log2n),但在极端情况下二叉平衡搜索树优于二叉搜索树,因为二叉平衡搜索树会自己调整平衡(后面会详细解释)

为什么是严格的绝对值为 1,不是 0 或者更大的数字?

若要求高度差为 0,即严格平衡,树的结构会过于 rigid(僵化)。每次插入或删除节点都可能需要大量调整操作,导致性能下降。允许高度差为 1,在保持较好平衡性的同时,减少了不必要的调整
若允许高度差为 2,树的平衡性会明显下降,可能出现一侧子树比另一侧高很多的情况,导致查找等操作的时间复杂度增加
所以平衡因子为 1 是最合适的

2.AVL树的结构

template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){ }
};
  • pair<K, V> _kv:用于存储键值对,pairC++ 标准库中的一个模板类,可将两个不同类型的值组合在一起
  • AVLTreeNode<K, V>* _left:指向左子节点的指针
  • AVLTreeNode<K, V>* _right:指向右子节点的指针
  • AVLTreeNode<K, V>* _parent:指向父节点的指针,这在调整树的平衡时很有用
  • int _bf:平衡因子(Balance Factor),用来记录该节点左右子树的高度差。平衡因子为 0 时表示左右子树高度相等;为 1 时表示右子树比左子树高 1;为 -1 时表示左子树比右子树高 1

3.AVL树的插入

	typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}//寻找节点插入位置Node* cur = _root;Node* parent = nullptr;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;}}//链接插入节点与AVL树cur = new Node(kv);if (parent->_kv.first < 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 == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转调整(...)}else{assert(false);}}return true;}

AVL 树的插入和二叉搜索树是很像的,先根据左大右小的原则,寻找插入节点的位置,然后判断父母节点与插入节点的关系,连接新节点,唯一不同的地方是平衡因子调节的部分,高度差是由右子树减去左子树得出的,可以总结出以下方法:

🚩 (1)新增在左,parent平衡因子减减
在这里插入图片描述

🚩 (2)新增在右,parent平衡因子加加

在这里插入图片描述

🚩 (3)更新后parent平衡因子 == 0

说明 parent 所在的子树的高度不变,不会影响祖先,不用再继续沿着到 root 的路径往上更新,然后循环结束

🚩 (4)更新后parent平衡因子 == 1 or -1

说明 parent 所在的子树的高度变化,会影响祖先,需要继续沿着到 root 的路径往上更新,循环继续

🚩 (5)更新后parent平衡因子 == 2 or -2

说明 parent 所在的子树的高度变化且不平衡,需要对parent所在子树进行旋转,让他平衡,然后循环结束

🔥值得注意的是: 如果平衡因子出现比 2 还大,比 -2 还小的数,说明之前的插入就已经出问题了

4.AVL树的旋转

4.1 左单旋

void RotateL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;Node* ppnode = parent->_parent;parent->_parent = cur;if (parent == _root){_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;
}

以下将根据一个图例来解释如何进行的左单旋:

在这里插入图片描述

左单旋顾名思义就是右子树太长,需要向左旋转形成平衡,平衡因子为 2 的节点定为 parent,其右节点为 curcur 的左节点为 curleft

  1. 调整 parent 的右子节点:parent 的右子节点设置成 curleft,若 curleft 不为空,就把 curleft 的父节点设置成 parent
  2. 调整 cur 的左子节点:cur 的左子节点设置成 parentppnodeparent 的父节点,把 parent 的父节点设置成 cur
  3. 调整根节点或者 ppnode 的子节点:parent 是根节点,那就把 cur 设为新的根节点,并且将 cur 的父节点设为 nullptr。若 parent 不是根节点,就依据 parentppnode 的左子节点还是右子节点,来更新 ppnode 的相应子节点为 cur,同时把 cur 的父节点设为 ppnode

4.2 右单旋

void RotateR(Node* parent)
{Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}Node* ppnode = parent->_parent;cur->_right = 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;
}

和左单旋类似,这里就不详细解释了

在这里插入图片描述

4.3 右左双旋

void RotateRL(Node* parent)
{Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}
}

右左双旋适用于新节点插入较高右子树的左侧的情况

在这里插入图片描述
30parent 节点,90cur 节点,60curleft 节点

先以 90 进行右单旋,再以 30 进行左单旋

双旋的重点是平衡节点的调整,根据多个例子可以知道,主要是看 curleft 节点的平衡因子

在这里插入图片描述

如果原来 curleft 平衡因子为 0 ,即 curleft 为新增节点导致的双旋,那么 curleftcurparent 平衡因子都为 0

在这里插入图片描述

如果原来 curleft 平衡因子为 1 ,即在 curleft 右边新增,那么 curcurleft 平衡因子都为 0parent 的平衡因子为 1

在这里插入图片描述

如果原来 curleft 平衡因子为 -1 ,即在 curleft 左边新增,那么 parentcurleft 平衡因子都为 0cur 的平衡因子为 1

4.4 左右双旋

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;}
}

和右左双旋类似,这里就不详细解释了

在这里插入图片描述

5.AVL树的删除

在实际开发中,虽然 AVL 树是一种自平衡的二叉搜索树,但其删除操作通常不被优先实现

AVL 树的核心特性是通过旋转操作(左旋、右旋、左右旋、右左旋)来保证树的高度平衡。在插入操作中,仅需从插入节点向上回溯至根节点,检查并调整路径上节点的平衡因子,最多进行两次旋转操作就能恢复树的平衡。然而,删除操作后,平衡的破坏可能会沿着从删除节点到根节点的路径向上传播,导致需要多次旋转操作来恢复平衡。这使得删除操作的实现逻辑变得异常复杂,需要仔细处理各种可能的情况

而且实现插入删除一般会使用 红黑树B树 等更优的数据结构

6.AVL树的高度

int Height(Node* root)
{if (root == nullptr)return 0;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

比较左子树和右子树的高度,取较大值并加 1(加上当前根节点),得到当前子树的高度

7.AVL树的平衡判断

bool IsBalance(Node* root)
{if (root == nullptr)return true;int leftHight = Height(root->_left);int rightHight = Height(root->_right);if (rightHight - leftHight != root->_bf){cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHight - leftHight) < 2&& IsBalance(root->_left)&& IsBalance(root->_right);
}

每遍历一个节点就对其左右子树的高度进行计算,然后判断是否绝对值小于 2

总结: AVL 树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过 1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对 AVL 树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑 AVL 树,但一个结构经常修改,就不太适合


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

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

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

相关文章

Verilog Test Fixture 时钟激励

1、占空比50%时钟产生 always begin<clock> 1b0 ;#<PERIOD/2> ;<clock> 1b1 ;#<PERIOD/2> ; end reg <clock> 1b0 ;alwaysbegin#<PERIOD/2> ;<clock> ~<clock> ;end 2…

从人体姿态到机械臂轨迹:基于深度学习的Kinova远程操控系统架构解析

在工业自动化、医疗辅助、灾难救援与太空探索等前沿领域&#xff0c;Kinova轻型机械臂凭借7自由度关节设计和出色负载能力脱颖而出。它能精准完成物体抓取、复杂装配和精细操作等任务。然而&#xff0c;实现人类操作者对Kinova机械臂的直观高效远程控制一直是技术难题。传统远程…

探秘数据中台:五大核心平台的功能全景解析

数据中台作为企业数据资产的 “智慧中枢”&#xff0c;通过整合数据处理全流程的核心功能&#xff0c;实现数据价值的深度挖掘与高效应用。以下从五大核心平台出发&#xff0c;全面拆解数据中台的功能架构与应用价值。 一、数据可视化平台&#xff1a;让数据 “开口说话” 1.…

深度 |提“智”向新,奔向未来——当前机器人产业观察

机器人踏着“猫步”在T台走秀、进入工厂协助造车&#xff0c;教育、医疗、城市管理等领域都有了机器人的帮助……今天&#xff0c;机器人已得到广泛应用&#xff0c;走进你我的生活。    伴随着技术日新月异&#xff0c;机器人产业加快提“智”向新。特别是今年以来&#xf…

桥隧坡灾害监测报警:用科技筑起生命安全的“智能防线”

.2024年&#xff0c;梅大高速茶阳路段高边坡塌方事件造成重大伤亡&#xff0c;举国痛心。这场悲剧再次敲响警钟&#xff1a;桥梁、隧道、边坡等高风险区域的实时监测与精准报警&#xff0c;已成为交通安全的生命线。如何用技术手段在灾害发生前“抢跑”&#xff0c;第一时间阻断…

【Python】一键提取视频音频并生成MP3的完整指南 by `MoviePy`

摘要 昨天&#xff0c; 我在让一个小朋友给我整理一次培训的视频的时候&#xff0c;我看到他把视频文件放到剪映里面处理。 我以为他要干什么呢&#xff0c; 还很期待&#xff0c;结果他只是为了导出音频而已。 于是就有了今天的这篇博客。 作为音视频处理领域的常用需求&…

PDF转长图工具

市面上的PDF转换工具数不胜数&#xff0c;福昕PDF、万兴PDF、Adobe Acrobat&#xff08;DC&#xff09;、PDF24等众多软件都具备PDF转图片的功能。然而&#xff0c;这些知名软件大多只能将单页PDF转换为单张图片&#xff0c;若要将PDF整体转换为一张长图&#xff0c;似乎并无此…

【Yolo精读+实践+魔改系列】Yolov3论文超详细精讲(翻译+笔记)

前言 前面咱们已经把 YOLOv1 和 YOLOv2 的老底都给掀了&#xff0c;今天轮到 YOLOv3 登场&#xff0c;这可是 Joseph Redmon 的“封神之作”。讲真&#xff0c;这哥们本来是搞学术的&#xff0c;结果研究的模型被某些军方拿去“整点活”——不是做人是做武器的那种活。于是他一…

算法攻略:接雨水问题的深度解析

算法攻略:接雨水问题的深度解析 一、引言 在算法的领域中,“接雨水”问题是一道经典且富有挑战性的题目。它不仅考查对数组操作的理解,更需要巧妙运用算法思想来解决看似复杂的实际场景问题。通过深入研究这一问题,我们能提升算法思维和编程能力,更好地应对各类算法难题。…

【Linux】Linux工具(1)

3.Linux工具&#xff08;1&#xff09; 文章目录 3.Linux工具&#xff08;1&#xff09;Linux 软件包管理器 yum什么是软件包关于 rzsz查看软件包——yum list命令如何安装软件如何卸载软件补充——yum如何找到要安装软件的下载地址 Linux开发工具Linux编辑器-vim使用1.vim的基…

springboot项目tomcat中加载不了

Spring Boot项目在Tomcat中加载不了的问题可能由多种原因引起&#xff0c;包括打包方式不正确、依赖配置错误、启动类配置不当等。以下是详细的解决方案&#xff1a; 1. 修改项目打包形式 将项目打包形式从jar改为war&#xff0c;以确保项目以正确的格式被Tomcat加载。在pom.…

Matlab 数控车床进给系统的建模与仿真

1、内容简介 Matlab217-数控车床进给系统的建模与仿真 可以交流、咨询、答疑 2、内容说明 略 摘 要:为提高数控车床的加工精度,对数控 车床进给系统中影响加工精度的主要因素进行了仿真分析研 动系统的数学模型,利用MATLAB软件中的动态仿真工具 究:依据机械动力学原理建立了…

Python Cookbook-7.8 使用 Berkeley DB 数据库

任务 你想将一些数据做持久化处理&#xff0c;而且也想体验一下BerkeleyDB数据库的简洁和高效。 解决方案 如果以前在你的计算机中安装过 BerkeleyDB&#xff0c;Python标准库附带的bsddb包(以及可选的 bsddb3&#xff0c;用于访间Berkeley DBrelease 3.2数据库)可以被用来作…

QT6 源(82):阅读与注释日历类型 QCalendar,本类并未完结,儒略历,格里高利历原来就是公历,

&#xff08;1&#xff09;本代码来自于头文件 qcalendar . h &#xff1a; #ifndef QCALENDAR_H #define QCALENDAR_H#include <limits>#include <QtCore/qglobal.h> #include <QtCore/qlocale.h> #include <QtCore/qstring.h> #include <QtCore/…

【C/C++】字符函数和字符串函数

文章目录 前言字符函数和字符串函数1.字符分类函数2.字符转换函数3.strlen的使用和模拟实现3.1 代码演示3.2 strlen返回值3.3 strlen的模拟实现 4.strcpy的使用和模拟实现4.1 代码演示4.2 模拟实现 5.strcat的使用和模拟实现5.1 代码演示5.2 模拟实现 6.strcmp的使用和模拟实现…

Spark-core-RDD入门

RDD基本概念 Resilient Distributed Dataset 叫做弹性分布式数据集&#xff0c;是Spark中最基本的数据抽象&#xff0c;是分布式计算的实现载体&#xff0c;代表一个不可变&#xff0c;可分区&#xff0c;里面的元素并行计算的集合。 - Dataset&#xff1a; 一个数据集合&…

缓存套餐-01.Spring Cache介绍和常用注解

一.Spring Cache 要使用直接导入坐标即可。 如何选择底层的缓存实现呢&#xff1f;只要导入对应的缓存坐标即可。如果要使用redis作为缓存实现&#xff0c;那么只需要导入redis的maven坐标。 二.常用注解 Cacheable&#xff1a;不光往缓存中写缓存数据&#xff0c;而且会从缓…

STM32智能空气净化器项目开发

一、项目概述 本空气净化器项目基于STM32F4系列微控制器&#xff0c;整合多传感器数据采集、环境参数显示、网络通信及执行机构控制等功能&#xff0c;实现智能化空气质量管理。项目采用FreeRTOS实时操作系统进行多任务调度&#xff0c;结合TFT触摸屏实现人机交互&#xff0c;…

[数据处理] 6. 数据可视化

&#x1f44b; 你好&#xff01;这里有实用干货与深度分享✨✨ 若有帮助&#xff0c;欢迎&#xff1a;​ &#x1f44d; 点赞 | ⭐ 收藏 | &#x1f4ac; 评论 | ➕ 关注 &#xff0c;解锁更多精彩&#xff01;​ &#x1f4c1; 收藏专栏即可第一时间获取最新推送&#x1f514;…

嵌入式学习笔记 - STM32 SRAM控制器FSMC

一 SRAM控制器内部结构图&#xff1a; 以下以512K SRAM芯片为例 二 SRAM地址矩阵/寻址方式&#xff1a; SRAM的地址寻址方式通过行地址与列地址交互的方式存储数据 三 STM32 地址映射 从STM32的地址映射中可以看出&#xff0c;FSMC控制器支持扩展4块外部存储器区域&#xff0…