【C++】AVL树

目录

  • 前言
  • 平衡二叉树的定义
  • AVL树的插入
    • AVL树插入的大致过程
    • 更新平衡因子
    • 调整最小不平衡因子
      • 左单旋
      • 右单旋
      • 左右双旋
      • 右左双旋
  • AVL树的删除
  • AVL树的查找

请添加图片描述

前言

前面我们在数据结构中学习了树,以及二叉树,还有二叉排序树,这节来学习平衡二叉树。

数据结构专题学习:数据结构学习
C++专题学习:深入学习C++

平衡二叉树的定义

  在使用二叉排序树时,二叉排序树的查找效率取决于树的高度,当构造二叉排序树的输入序列是有序的时候,它就会形成只有左子树(从大到小输入)或者只有右子树(从小到大输入)的单支树,此时二叉排序树的性能显著变坏
  因此,为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除节点时,要保证任意节点的左、右子树高度差的绝对值不超过1,将这样的树就称为平衡二叉树,即AVL树。
  它给树中的每个节点都定义了一个平衡因子(bf)。
节点的平衡因子=左子树高度-右子树高度。(当然也有右子树高度-左子树高度的,只要不违背高度差的绝对值不超过一即可)
平衡二叉树具有以下几个性质:

  • AVL树是一棵二叉排序树
  • AVL树的左右子树也都是AVL树
  • AVL树节点的平衡因子的值只能为-1,0,1

AVL树如下图所示:
在这里插入图片描述

AVL树是一个三叉链结构,不仅有指向左右孩子节点的指针,还有指向父节点的指针,AVL树的代码定义如下:

template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv;//std::pair是C++标准库中的一个模板类,它能把两个不同类型的值组合成一个单元,这两个值分别由 first 和 second 成员访问。AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr),_bf(0){}
};

AVL树的插入

AVL树插入的大致过程

  1. 在插入值时会按照二叉排序树的插入规则进行插入。
  2. 插入新节点后,会影响祖先节点的高度,即影响祖先的平衡因子,因此要检查其插入路径上的节点是否因为此次操作而导致了不平衡。
  3. 如果导致了不平衡,也就是平衡因子变为了2或-2,此时就要调整这棵子树,进行旋转,从而调节平衡因子从新插入的节点开始往上找,找到第一个不平衡的节点,调整以该节点为根的最小不平衡子树。
  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){//如果要插入节点的值大于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);//判断插在parent节点的左边还是右边if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_left)parent->_bf++;elseparent->_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){// 不平衡了,旋转处理if (parent->_bf == 2 && cur->_bf == 1){//左子树高了,要右旋RotateR(parent);}else if (parent->_bf == -2 && cur->_bf == -1){//右子树高了,要左旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//左孩子的右子树高了,先左旋再右旋RotateLR(parent);}else{//右孩子的左子树高了,先右旋再左旋RotateRL(parent);}break;}else{assert(false);}}return true;}

调整最小不平衡因子

  为了既能保证搜索树的规则,又能降低树的高度,调整平衡因子,总共有四种旋转操作:左单旋/右单旋/左右双旋/右左双旋。下面依次来解释。
  首先,在调整之前,我们先要找到第一个不平衡的节点,从上面的调整平衡因子就可以知道。然后以该节点作为根节点,它所对应的子树就是我们要调整的最小不平衡子树。如下图:
在这里插入图片描述
  因为==只要把最小的这棵不平衡子树让它恢复之后,它的祖先节点的平衡因子也都会恢复,==因此在插入一个新节点导致不平衡之后,我们只需要调整最小的这棵子树,就可以让其它节点也恢复平衡。

左单旋

  当在节点A的右孩子的右子树上插入了一个新节点后,导致了A的平衡因子由-1减至-2,导致以A为节点的子树失去平衡,此时就需要一次左单旋操作。如下图所示:
在这里插入图片描述

图中:
H代表各个子树的高度都为H,至于为什么都为H,而不是H-1或者H+1,大家可以自行带入,找平衡因子就可以知道了。
RR插入,指的是在右孩子的右子树上插入了一个新节点导致树的不平衡。
BL:B的左孩子,BR:B的右孩子,AL:A的左孩子
二叉排序树的特性:左子树节点值 < 根节点值 < 右子树节点值

  左单旋的过程为:将A的右孩子B左上旋转代替A成为根节点,将A节点左下旋转成为B的左孩子,而B的原左子树成为A的右子树。

void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if(subRL)//如果subRL,即右孩子的左子树不为空,则更新它的父节点subRL->_parent = parent;//找到原来父节点的父节点Node* parentParent = parent->_parent;//将父节点更新到原来右孩子的左边subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr)//如果原来父节点就是根节点,则更新根节点{_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}//更新平衡因子。parent->_bf = subR->_bf = 0;}

可能有些复杂,学会大致思路也行,大致思路如下(设节点A是f,节点B是p,节点A的父节点是gf):

  1. f->left = p -> right;
  2. p->right = f;
  3. gf->left/right = p;

右单旋

  当在节点A的左孩子的左子树上插入了一个新节点后,导致了A的平衡因子由1增至2,导致以A为节点的子树失去平衡,此时就需要一次右单旋操作。如下图所示:
在这里插入图片描述

  右单旋的过程为:将A的左孩子B向右上旋转代替A成为根结点,将A右下旋转成为B的右孩子,而B的右子树变为A的左子树。

void  RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parentParent == nullptr){_root = subL;subL->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_bf = subL->_bf = 0;}

大致思路如下(设节点A时f,节点B是p,节点A的父节点是gf):

  1. f->right = p->left;
  2. p->left = f;
  3. gf->left/right = p;

左右双旋

  当在节点A的左孩子的右子树上插入了一个新节点后,导致了A的平衡因子由1增至2,导致以A为节点的子树失去平衡,此时就需要两次旋转操作,先左旋一次,再进行右旋。如下图所示:
在这里插入图片描述
在这里插入图片描述
  左右双旋的过程为:先将A的左孩子B的右子树的根节点C向左上旋转提升到B的位置,然后把C向右上提升到A的位置。

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 1){subL->_bf = 0;subLR->_bf = -1;parent->_bf = 0;}else if (bf == -1){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}elseassert(false);
}

右左双旋

  当在节点A的右孩子的左子树上插入了一个新节点后,导致了A的平衡因子由-1减至-2,导致以A为节点的子树失去平衡,此时就需要两次旋转操作,先右旋转一次,再进行左旋。如下图所示:
在这里插入图片描述
在这里插入图片描述

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

AVL树的删除

AVL树的删除有以下几个步骤:

  1. 用二叉排序树的方法对节点w执行删除操作。

  2. 若导致了不平衡,则从节点w开始向上回溯,找到第一个不平衡的节点z(最小不平衡子树),找到节点z的高度最高的孩子y(即"个头"最高的儿子),再找到节点y的高度最高的孩子x(即"个头"最高的孙子)。

  3. 对以z为根的子树进行平衡调整:
    ①孙子在LL,则进行右单旋
    ②孙子在LR,则进行左右双旋
    ③孙子在RR,则进行左单旋
    ④孙子在RL,则进行右左双旋

  4. 若调整后子树高度减1,则可能需要对z的祖先节点进行平衡调整直至回溯到根节点。

二叉排序树的删除操作可参考下图:
在这里插入图片描述
删除操作如下图:
在这里插入图片描述

AVL树的查找

在AVL树上查找的过程与BST数相同。因此查找过程中,进行关键字比较次数不超过树的深度。假设nh表示深度为h的AVL树中含有的最少节点数。显然有n0=1,n1=1,n2=2并且有nh=nh-2+nh-1+1,如下图所示,则可以依次推出深度为h的AVL树含有的最少节点。含有n个节点的AVL树的最大深度为O(log~2~n),因此平均查找效率为O(log~2~n)。
在这里插入图片描述
查找的代码如下:

Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;
}

小tips:
当关键字以有序的顺序插入初始为空的AVL树中,若关键字个数为n=2h-1时,该二叉树一定是一棵满二叉树。


感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。本篇还涵盖了王道的内容与图片。
请添加图片描述

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

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

相关文章

【洛谷题单】暴力枚举(上)

【前情提要】 此文章包含洛谷题单的枚举题单&#xff0c;共14题&#xff0c;本篇7道题&#xff0c;主要分析思路&#xff0c;并通过这几道题目&#xff0c;进行总结有关枚举的内容。所以内容比较多&#xff0c;可以先收藏起来&#xff0c;慢慢看。 题单链接&#xff1a;暴力枚…

JVM类加载过程详解

文章目录 前言1.加载2.链接验证文件格式验证元数据验证字节码验证符号引用验证 准备解析 3.初始化4.类卸载 前言 类从被加载到虚拟机内存中开始到卸载出内存为止&#xff0c;它的整个生命周期可以简单概括为 7 个阶段&#xff1a;加载&#xff08;Loading&#xff09;、验证&a…

python之并发编程

并发编程介绍 串行、并行与并发的区别 进程、线程、协程的区别 1. 进程 (Process) 定义&#xff1a;进程是操作系统为运行中的程序分配的基本单位。每个进程都有独立的地址空间和资源&#xff08;如内存、文件句柄等&#xff09;。特点&#xff1a; 进程是资源分配的基本单位…

批量优化与压缩 PPT,减少 PPT 文件的大小

我们经常能够看到有些 PPT 文档明明没有多少内容&#xff0c;但是却占用了很大的空间&#xff0c;存储和传输非常的不方便&#xff0c;这时候通常是因为我们插入了一些图片/字体等资源文件&#xff0c;这些都可能会导致我们的 PPT 文档变得非常的庞大&#xff0c;今天就给大家介…

centos 7 LVM管理命令

物理卷&#xff08;PV&#xff09;管理命令 pvcreate&#xff1a;用于将物理磁盘分区或整个磁盘创建为物理卷。 示例&#xff1a;sudo pvcreate /dev/sdb1 解释&#xff1a;将 /dev/sdb1 分区创建为物理卷。 pvdisplay&#xff1a;显示物理卷的详细信息&#xff0c;如大小、所属…

b站视频提取mp4方案

引言 对于b站视频&#xff0c;有些视频是不能提取字幕的&#xff0c;所以我们想把对应的视频下载下来&#xff0c;然后进行对应的本地处理&#xff0c;获得所需的自由处理&#xff0c;吞食视频。 整体思路 下载b站客户端 ----> 把缓存路径修改------> 下载所需视频---…

springboot在feign和线程池中使用TraceId日志链路追踪(最终版)-2

文章目录 简述问题feign调用时给head加入traceIdFeignConfig配置FeignConfig 局部生效feign拦截器和配置合并为一个文件&#xff08;最终版&#xff09;feign异步调用拦截器配置[不常用] 使用TTL自定义线程池为什么需要TransmittableThreadLocal&#xff1f; 总结参考和拓展阅读…

MySQL数据库单表与多表查询

一.单表查询 1.创建用于数据查询的数据库表 CREATE TABLE worker (部门号 int(11) NOT NULL,职工号 int(11) NOT NULL,工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(10) NOT NULL DEFAULT 群众,姓名 varchar(20) NOT NULL,出生日期 date NOT NULL,PRIM…

海外紧固件市场格局与发展趋势研究报

一、引言 紧固件作为各类机械装备、建筑结构以及电子设备中不可或缺的基础性零部件&#xff0c;在国民经济的各个领域都有着广泛应用。其市场动态与全球经济发展态势以及各行业的兴衰紧密相连。在全球化进程不断加速、产业分工日益精细的大背景下&#xff0c;深入研究海外紧固…

【多学科稳定EI会议大合集】计算机应用、通信信号、电气能源工程、社科经管教育、光学光电、遥感测绘、生物医学等多学科征稿!

在当今科技高速发展的时代&#xff0c;多学科领域的学术交流与融合显得尤为重要。以下是稳定EI会议合集&#xff0c;涵盖计算机、信息通信、电气能源、社科经管教育、光学遥感、生物医学等多个学科领域。 会议皆已通过国际知名出版社出版审核&#xff0c;EI检索稳定&#xff0…

【深度学习新浪潮】展平RVQ技术详解

展平 RVQ(Flattened Residual Vector Quantization)是一种基于矢量量化(Vector Quantization, VQ)的技术,主要用于高效地表示和压缩数据(例如图像、音频或文本嵌入)。它结合了**残差矢量量化(Residual Vector Quantization, RVQ)**的思想与“展平”操作,从而进一步优…

【第23节】windows网络编程模型(WSAEventSelect模型)

目录 引言 一、WSAEventSelect模型概述 二、 WSAEventSelect模型的实现流程 2.1 创建一个事件对象&#xff0c;注册网络事件 2.2 等待网络事件发生 2.3 获取网络事件 2.4 手动设置信号量和释放资源 三、 WSAEventSelect模型伪代码示例 四、完整实践示例代码 引言 在网…

LlamaFactory部署及模型微调【win10环境】

1.Llama-Factory简介 LLaMA-Factory&#xff0c;全称 Large Language Model Factory&#xff0c;旨在简化大模型的微调过程&#xff0c;帮助开发者快速适应特定任务需求&#xff0c;提升模型表现。它支持多种预训练模型和微调算法&#xff0c;适用于智能客服、语音识别、机器翻…

Jmeter简介、学习目标及安装启动

1. 简介 JMeter 是 Apache 组织使用 Java 开发的一款测试工具&#xff1a;可以用于对服务器、网络或对象模拟巨大的负载&#xff1b;通过创建带有断言的脚本来验证程序是否能返回期望的结果。 1&#xff09;优点&#xff1a;开源、免费&#xff1b;跨平台&#xff1b;支持多协…

无参数读文件和RCE

什么是无参数&#xff1f; 无参数&#xff08;No-Argument&#xff09;的概念&#xff0c;顾名思义&#xff0c;就是在PHP中调用函数时&#xff0c;不传递任何参数。我们需要利用仅靠函数本身的返回值或嵌套无参数函数的方式&#xff0c;达到读取文件或远程命令执行&#xff0…

细胞内与细胞间网络整合分析!神经网络+细胞通讯,这个单细胞分析工具一箭双雕了(scTenifoldXct)

生信碱移 细胞间-细胞内通讯网络分析 scTenifoldXct&#xff0c;一种结合了细胞内和细胞间基因网络的计算工具&#xff0c;利用 scRNA-seq 数据检测细胞间相互作用。 单细胞 RNA 测序&#xff08;scRNA-seq&#xff09;能够以稳健且可重复的方式同时收集数万个细胞的转录组信息…

怎么处理 Vue 项目中的错误的?

一、错误类型 任何一个框架,对于错误的处理都是一种必备的能力 在Vue 中,则是定义了一套对应的错误处理规则给到使用者,且在源代码级别,对部分必要的过程做了一定的错误处理。 主要的错误来源包括: 后端接口错误代码中本身逻辑错误二、如何处理 后端接口错误 通过axi…

05.AI搭建preparationの(transformers01)BertTokenizer实现分词编码

一、下载 bert-base-chinese镜像下载 二、简介作用&#xff1a; 模型每个参数占用的字节大小模型大小模型大小层数头数GPT-14 个字节的 FP32 精度浮点数117M446MB1212GPT-22 个字节的 FP161.5亿到1.75亿0.5GB到1.5GB4816GPT-32 个字节的 FP161.75万亿&#xff08;17500亿&a…

工业4G路由器赋能智慧停车场高效管理

工业4G路由器作为智慧停车场管理系统通信核心&#xff0c;将停车场内的各个子系统连接起来&#xff0c;包括车牌识别系统、道闸控制系统、车位检测系统、收费系统以及监控系统等。通过4G网络&#xff0c;将这些系统采集到的数据传输到云端服务器或管理中心&#xff0c;实现信息…

git 基础操作

1. git 的安装 与 卸载 1.1. git 的安装 判断是否安装 git git --version 安装 git: centos: sudo yum -y install git ubuntu: sudo apt-get install git -y windows: 3.安装git和图形化界面工具_哔哩哔哩_bilibili 1.2. git 的卸载 判断是否安装 git git --version…