当前位置: 首页 > news >正文

C++ AVL树 详细讲解

目录

一、AVL树的概念

二、AVL树的实现

1.AVL树节点的定义

2.AVL树的插入

3.AVL树的旋转

4.AVL树的验证

三、AVL树的性能

四、完结撒❀


一、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但 如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家 G.M.Adelson-Velskii
E.M.Landis 1962 发明了一种解决上述问题的方法: 当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可降低树的高度,从而减少平均 搜索长度。
一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
● 它的左右子树都是 AVL
● 左右子树高度之差 ( 简称平衡因子 ) 的绝对值不超过 1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是 AVL 树。如果它有 n 个结点,其高度可保持在
O(log2 n) ,搜索时间复杂度 O(log2 n)

二、AVL树的实现

1.AVL树节点的定义

AVL树节点的定义:

template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;   // 该节点的左孩子AVLTreeNode<T>* _pRight;  // 该节点的右孩子AVLTreeNode<T>* _pParent; // 该节点的双亲T _data;int _bf;                  // 该节点的平衡因子
};

2.AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么
AVL 树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
bool Insert(const pair<K, V>& kv)
{// 1. 先按照二叉搜索树的规则将节点插入到AVL树中if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{//插入相同值return false;}}//找到cur所在位置cur = new Node(kv);if (parent->_kv.first > cur->_kv.first){parent->_left = cur;cur->_parent = parent;}else{parent->_right = cur;cur->_parent = parent;}// 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否//破坏了AVL树的平衡性/*pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可此时:pParent的平衡因子可能有三种情况:0,正负1, 正负21. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足
AVL树的性质,插入成功2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此
时以pParent为根的树的高度增加,需要继续向上更新3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理*///更新平衡因子while (parent){if (parent->_left == cur){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//二叉树高度没问题,更新结束break;}else if (parent->_bf == 1 || parent->_bf == -1){// 插入前双亲的平衡因子是0,插入后双亲的平衡因子为1 或者 -1 ,说明以双亲为根的二叉树// 的高度增加了一层,因此需要继续向上调整cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 2){//双亲的平衡因子为正负2,违反了AVL树的平衡性//二叉树平衡被破坏,需要旋转完成平衡//判断是右单旋还是左单旋还是双旋//右单旋if (parent->_bf == -2 && cur->_bf == -1){//...}//左单旋else if (parent->_bf == 2 && cur->_bf == 1){//...}//左右双旋else if (parent->_bf == -2 && cur->_bf == 1){//...}//右左双旋else if (parent->_bf == 2 && cur->_bf == -1){//...}}else{//理论上不会出现这种状况assert(false);}}return true;
}

3.AVL树的旋转

如果在一棵原本是平衡的 AVL 树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同, AVL 树的旋转分为四种:
1) 新节点插入较高左子树的左侧---左左:右单旋

/*上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左
子树增加了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子
树增加一层,即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有
右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点
的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:1. 30节点的右孩子可能存在,也可能不存在2. 60可能是根节点,也可能是子树如果是根节点,旋转完成后,要更新根节点如果是子树,可能是某个节点的左子树,也可能是右子树此处可举一些详细的例子进行画图,考虑各种情况,加深旋转的理解
*/
void _RotateR(Node Parent)
{// SubL: Parent的左孩子// SubLR: Parent左孩子的右孩子,注意:该Node SubL = Parent->_Left;Node SubLR = SubL->_Right;// 旋转完成之后,30的右孩子作为双亲的左孩子Parent->_Left = SubLR;// 如果30的左孩子的右孩子存在,更新亲双亲if (SubLR)SubLR->_Parent = Parent;// 60 作为 30的右孩子SubL->_Right = Parent;// 因为60可能是棵子树,因此在更新其双亲前必须先保存60的双亲Node Parent = Parent->_Parent;// 更新60的双亲Parent->_Parent = SubL;// 更新30的双亲SubL->_Parent = Parent;// 如果60是根节点,根新指向根节点的指针if (NULL == Parent){_root = SubL;SubL->_Parent = NULL;}else{// 如果60是子树,可能是其双亲的左子树,也可能是右子树if (Parent->_Left == Parent)Parent->_Left = SubL;elseParent->_Right = SubL;}// 根据调整后的结构更新部分节点的平衡因子Parent->_bf = SubL->_bf = 0;
}

2) 新节点插入较高右子树的右侧---右右:左单旋

//左单旋
void LNode(Node* parent)
{/*if (parent == _root){Node* pparent = nullptr;}else{Node* pparent = parent->_parent;}*/Node* pparent = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;parent->_left = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;parent->_parent = subR;if (pparent){subR->_parent = pparent;if (pparent->_left = parent){pparent->_left = subR;}else{pparent->_right = subR;}}else{_root = subR;subR->_parent = nullptr;}parent->_bf = subR->_bf = 0;
}

3) 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即: 先对 30 进行左单旋,然后再对 90 进行右单旋 ,旋转完成后再
考虑平衡因子的更新。
// 旋转之前,60的平衡因子可能是-1/0/1,旋转完成之后,根据情况对其他节点的平衡因子进
//行调整
void _RotateLR(Node Parent)
{Node SubL = Parent->_Left;Node SubLR = SubL->_Right;// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子int bf = SubLR->_bf;// 先对30进行左单旋_RotateL(Parent->_Left);// 再对90进行右单旋_RotateR(Parent);if (1 == bf)SubL->_bf = -1;else if (-1 == bf)Parent->_bf = 1;
}

4) 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

//右左双旋
void RLNode(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RNode(parent->_right);LNode(parent);if (bf == 1){subRL->_bf = 0;subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){subRL->_bf = 0;subR->_bf = 1;parent->_bf = 0;}else if (bf == 0){subRL->_bf = 0;subR->_bf = 0;parent->_bf = 0;}else{//理论没有该状况assert(false);}
}
总结:
假如以 Parent 为根的子树不平衡,即 Parent 的平衡因子为 2 或者 -2 ,分以下情况考虑
1)Parent 的平衡因子为 2 ,说明 Parent 的右子树高,设 Parent 的右子树的根为 SubR
SubR 的平衡因子为 1 时,执行左单旋
SubR 的平衡因子为 -1 时,执行右左双旋
2)Parent 的平衡因子为 -2 ,说明 Parent 的左子树高,设 Parent 的左子树的根为 SubL
SubL 的平衡因子为 -1 是,执行右单旋
SubL 的平衡因子为 1 时,执行左右双旋
旋转完成后,原 Parent 为根的子树个高度降低,已经平衡,不需要再向上更新。

4.AVL树的验证

AVL 树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证 AVL 树,可以分两步:
        1. 验证其为二叉搜索树
            如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
        2. 验证其为平衡树
            ● 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子 )
            ● 节点的平衡因子是否计算正确
int _size(Node* root)
{return root == nullptr ? 0 : _size(root->_left) + _size(root->_right) + 1;
}int _Height(Node* root)
{if (root == nullptr){return 0;}return max(_Height(root->_left), _Height(root->_right)) + 1;
}bool _IsBalance(Node* root)
{if (root == nullptr){return true;}int LeftHeight = _Height(root->_left);int RightHeight = _Height(root->_right);if (abs(LeftHeight - RightHeight) >= 2){return false;}//可以顺便再检查一下平衡因子if (abs(LeftHeight - RightHeight) != root->_bf){cout << root->_kv.first << endl;return false;}return _IsBalance(root->_left) && _IsBalance(root->_right);
}

三、AVL树的性能

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

四、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤

相关文章:

C++ AVL树 详细讲解

目录 一、AVL树的概念 二、AVL树的实现 1.AVL树节点的定义 2.AVL树的插入 3.AVL树的旋转 4.AVL树的验证 三、AVL树的性能 四、完结撒❀ 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但 如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查 …...

Faster R-CNN:端到端的目标检测网络

本文回顾了由微软研究人员开发的 Faster R-CNN 模型。Faster R-CNN 是一种用于物体检测的深度卷积网络&#xff0c;在用户看来&#xff0c;它是一个单一的、端到端的统一网络。该网络可以准确快速地预测不同物体的位置。为了真正理解 Faster R-CNN&#xff0c;我们还必须快速概…...

如何给 MySQL 表和列授予权限?(官方版)

目录 授予表级别权限 授予列级别权限 如何给MySQL表和列授予权限是MySQL数据操作中非常重要的步骤&#xff0c;也是企业级使用MySQL数据库的起步点&#xff0c;以下分别参照官方教程整理的MySQL数据库的权限操作。 以下的语句可以直接使用MySQL的命令行进行操作&#xff08;如何…...

攻防世界testre做法(考点:base58)

在做这道题目之前&#xff0c;我们先来简单了解一下base64加密和base58加密&#xff0c;先来说一些预备知识&#xff0c;bit为1个位&#xff0c;即一个0或1&#xff0c;八个位组成一个字节&#xff0c;即八个二进制数。 base64编码原理&#xff1a;1&#xff0c;在使用base64加…...

计算机视觉与模式识别实验1-1 图像的直方图平衡

文章目录 &#x1f9e1;&#x1f9e1;实验流程&#x1f9e1;&#x1f9e1;1.读入图像‘rice.png’&#xff0c;在一个窗口中显示灰度级n64&#xff0c;128和256的图像直方图。2.调解图像灰度范围&#xff0c;观察变换后的图像及其直方图的变化。3.分别对图像‘pout.tif’和‘ti…...

【C++课程学习】:C++入门(函数重载)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f308;函数重载&#xff1a; &#x1f349;1.参数个数不同&#xff1a; &#x1f349;2.参数…...

skywalking介绍及搭建

链路追踪框架比对&#xff1a; skywalking安装部署&#xff1a; 下载地址&#xff1a;Downloads | Apache SkyWalking 配置微服务与skywalking整合&#xff1a; copy agent/optional-plugins/apm-spring-cloud-getway-xx.jar到plugins&#xff0c;然后重启skywalking 监控界面…...

分析示例 | Simufact焊接工艺仿真变形精确预测汽车结构

导语 焊接是汽车制造过程中一个关键环节&#xff0c;白车身、发动机、底盘和变速箱等都离不开焊接工艺的应用&#xff0c;主要涉及气保焊、电阻点焊、激光焊、电子束焊等多种焊接工艺。由于汽车车型众多、成形结构复杂、汽车制造质量、效率、成本等方面的综合要求。如何高效、…...

模式识别选择题

影响K-均值聚类算法效果的主要因素之一是什么&#xff1f; A. 初始聚类中心的选取 B. 样本输入顺序 C. 模式相似性测度 D. 分类准则 答案&#xff1a;A支持向量机&#xff08;SVM&#xff09;在处理非线性问题时&#xff0c;通常使用什么方法&#xff1f; A. 引入核函数 B. 增加…...

【Java基础】线程方法

start()&#xff1a;启动线程&#xff0c;使线程进入就绪状态。 run()&#xff1a;线程执行的代码逻辑&#xff0c;需要重写该方法。 停止线程 void interrupt() 中断线程&#xff0c;让它重新去争抢cpu 如果目标线程长时间等待&#xff0c;则应该使用interrupt方法来中断等待…...

C++之动态数组

C给我们提供了一个叫Vector的类&#xff0c;这个Vector在std命名空间中。这个Vector有点像一个集合&#xff0c;一个不强制其实际元素具有唯一性的集合&#xff0c;和数组一样&#xff0c;但是和C普通的数组又不太一样&#xff0c;和标准的数组不同当你创建Vector时&#xff0c…...

使用 image-combiner 开源项目实现对海报图片的生成

1&#xff1a;gitee 项目地址 image-combiner: ImageCombiner是一个专门用于Java服务端图片合成的工具&#xff0c;没有很复杂的功能&#xff0c;简单实用&#xff0c;从实际业务场景出发&#xff0c;提供简单的接口&#xff0c;几行代码即可实现图片拼合&#xff08;当然用于…...

【缓存】框架层常见问题和对策

缓存是为了加快读写速度&#xff0c;再了解redis这类框架层的缓存应用之前&#xff0c;我们不妨先思考下操作系统层面的缓存解决方案&#xff0c;这样有助于我们更深的理解缓存&#xff0c;哪些是系统层面的&#xff0c;哪些是服务层面。 以下是一些常见的缓存问题及其解决方案…...

【FAS】《CN103106397B》

原文 CN103106397B-基于亮瞳效应的人脸活体检测方法-授权-2013.01.19 华南理工大学 方法 / 点评 核心方法用的是传统的形态学和模板匹配&#xff0c;亮点是双红外发射器做差分 差分&#xff1a;所述FPGA芯片控制两组红外光源&#xff08;一近一远&#xff09;交替亮灭&…...

3D按F3为什么显示不出模型?---模大狮模型网

对于3D建模软件的用户来说&#xff0c;按下F3键通常是用来显示或隐藏模型的功能之一。然而&#xff0c;有时当按下F3键时&#xff0c;却无法正确显示模型&#xff0c;这可能会让用户感到困惑。模大狮将探讨这种情况发生的可能原因以及解决方法&#xff0c;帮助设计师们更好地理…...

C++设计模式——Adapter适配器模式

一&#xff0c;适配器模式简介 适配器模式是一种结构型设计模式&#xff0c;用于将已有接口转换为调用者所期望的另一种接口。 适配器模式让特定的API接口可以适配多种场景。例如&#xff0c;现有一个名为"Reader()"的API接口只能解析txt格式的文件&#xff0c;给这…...

Python文本处理利器:jieba库全解析

文章目录 Python文本处理利器&#xff1a;jieba库全解析第一部分&#xff1a;背景和功能介绍第二部分&#xff1a;库的概述第三部分&#xff1a;安装方法第四部分&#xff1a;常用库函数介绍1. 精确模式分词2. 全模式分词3. 搜索引擎模式分词4. 添加自定义词典5. 关键词提取 第…...

【C/C++】C语言如何实现类似C++的智能指针?

在C中&#xff0c;智能指针是为了自动化资源管理而引入的工具。比如std::unique_ptr和std::shared_ptr等&#xff0c;它们管理着所持有对象的生命周期&#xff0c;可以在智能指针被销毁时自动释放其所持有的资源。在C语言中&#xff0c;虽然没有直接的智能指针概念&#xff0c;…...

九大微服务监控工具详解

Prometheus Prometheus 是一个开源的系统监控、和报警工具包&#xff0c;Prometheus 被设计用来监控“微服务架构”。 主要解决&#xff1a; 监控和告警&#xff1a;Prometheus 可以对系统、和应用程序进行实时监控&#xff0c;并在出现问题时发送告警&#xff1b;数据收集和…...

java aliyun oss上传和下载工具类

java aliyun oss上传和下载工具类 依赖 <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.8.0</version></dependency>工具类 import com.alibaba.fastjson.JSON; import c…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...