详解—[C++ 数据结构]—AVL树
目录
一.AVL树的概念
二、AVL树节点的定义
三、AVL树的插入
3.1插入方法
四、AVL树的旋转
1. 新节点插入较高左子树的左侧---左左:右单旋
2. 新节点插入较高右子树的右侧---右右:左单旋
3.新节点插入较高左子树的右侧---左右:先左单旋再右单旋
4.新节点插入较高右子树的左侧---右左:先右单旋再左单旋
5.AVL树的插入代码
五、AVL树的验证
六、AVL树的性能
一.AVL树的概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)平衡因子 = 右子树高度 - 左子树高度
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 ,搜索时
间复杂度O( )。
二、AVL树节点的定义
AVL树每个节点都会记录他的左右孩子和父节点
并且每个节点记录一下自己的平衡因子
用模板T存储他的数据
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; // 该节点的平衡因子
};
三、AVL树的插入
对于
AVL
树的插入,因为它是要结合AVL
树的旋转的,所以在本文中,AVL
树的插入和AVL
树的旋转合起来才是完整的插入过程,所以这里我们主要讲一下插入的大体的一个过程,具体插入的细节及代码实现后面实现
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
3.1插入方法
1. 先按照二叉搜索树的规则将节点插入到AVL树中
2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了
AVL树的平衡性
新节点插入后,他的父节点的平衡因子一定需要调整,在插入之前,父节点
的平衡因子分为三种情况:-1,0, 1,分以下两种情况:
1. 如果新节点插入到父节点的左侧,只需给父节点的平衡因子-1即可
2. 如果新节点插入到父节点的右侧,只需给父节点的平衡因子+1即可
此时:父节点的平衡因子可能有三种情况:0,正负1, 正负2
1. 如果父节点的平衡因子为0,说明插入之前父节点的平衡因子为正负1,插入后被调整成0,此
时满足AVL树的性质,插入成功
2. 如果父节点的平衡因子为正负1,说明插入前父节点的平衡因子一定为0,插入后被更新成正负1,此时以父节点为根的树的高度增加,需要继续向上更新
3. 如果父节点的平衡因子为正负2,则父节点的平衡因子违反平衡树的性质,需要对其进行旋转
处理
四、AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧---左左:右单旋
最终,根据我们图上所画的这种右单选的情况,我们可以按照上图写出右旋转的代码:
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;// 更新节点之间的连接关系parent->_left = subLR;if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* pparent = parent->_parent;parent->_parent = subL;if (!pparent){_root = subL;subL->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subL;}else if (pparent->_right == parent){pparent->_right = subL;}subL->_parent = pparent;}// 更新平衡因子parent->_bf = subL->_bf = 0;}
根据上图判断:
我们定义了父亲的左subL和父亲左的右subLR
首先根据上图第一步,把父亲左的右节点给父亲的左,如果subLR不为空,更新一下subLR的父节点
然后把parent链接在subL的右边,(记录一下父亲的父亲(pparent),一会需要subL更新父节点) 更改父亲的父节点为subL
下面就开始更新subL的父节点了
第一步判断旋转前的person是不是根节点,如果是根节点的父亲(pparent)为空,然后把根节点更新为subL,把subL的父节点置为空
如果不是根节点,判断以前pparent的左边还是右边是父亲(parent),让pparent指向父亲改为指向subL,再把subL的父节点更新为pparent
最后,更新平衡因子,把parent和subL的平衡因子置为0
2. 新节点插入较高右子树的右侧---右右:左单旋
最终,根据我们图上所画的这种右单选的情况,我们可以按照上图写出左旋转的代码:
// 左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;// 更新节点之间的连接关系parent->_right = subRL;if (subRL)// subRL不为空才需要更新它的父亲{subRL->_parent = parent;}subR->_left = parent;Node* pparent = parent->_parent;parent->_parent = subR;if (!pparent)// parent为根时的处理{_root = subR;subR->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}// 更新平衡因子parent->_bf = subR->_bf = 0;}
对于左单旋解释,左单旋跟右单旋 两者非常类似,所以这里不再花费篇幅去讲解.
3.新节点插入较高左子树的右侧---左右:先左单旋再右单旋
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
// 左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int flag = subLR->_bf;// 记录subLR的平衡因子,最后要依据它来更新其他节点的平衡因子// 依次旋转RotateL(subL);RotateR(parent);// 根据subLR平衡因子的值更新不同插入情况下的平衡因子if (flag == 1)// 说明是在subLR的右子树插入的,那么subLR的左子树变为subL的右子树,subL平衡因子变为-1,subLR和parent的为0{subL->_bf == -1;}else if (flag == -1)// 说明是在subLR的左子树插入的,subLR的右子树最后会被分给parent作为左子树,parent的平衡因子变为-1,subL和subLR的平衡因子变为0{parent->_bf == 1;}}
4.新节点插入较高右子树的左侧---右左:先右单旋再左单旋
void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int flag = subRL->_bf;// 依次旋转RotateR(subR);RotateL(parent);// 更新平衡因子if (flag == 1){parent->_bf == -1;}else if (flag == -1){subR->_bf == 1;}}
5.AVL树的插入代码
bool Insert(const pair<k, v>& kv){// 空树的话,就让插入的那个节点作为根if (!_root){_root = new Node(kv);return true;}// 不是空树,就按照搜索树的性质找到插入的位置和它的父亲Node* cur = _root;Node* parent = nullptr;while (cur){parent = cur;if (cur->_kv.first == kv.first){return false;}else if (cur->_kv.first > kv.first){cur = cur->_left;}else{cur = cur->_right;}}// 创建要插入的节点Node* newNode = new Node(kv);// 更新关系,插入节点newNode->_parent = parent;if (parent->_kv.first < newNode->_kv.first){parent->_right = newNode;}else{parent->_left = newNode;}cur = newNode;parent = cur->_parent;while (parent){// 向上更新平衡因子if (cur == parent->_left){--(parent->_bf);}else{++(parent->_bf);}// 检查是否需要调整// 0的话就平衡了// -1或1的话还要向上更新// -2或2的话需要旋转处理if (parent->_bf == 0)// 平衡因子为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){if (cur->_bf == 1)// 右子树的右子树也高 --> 左单旋{RotateL(parent);}else if (cur->_bf == -1)// 右子树的左子树也高 --> 右左双旋{RotateRL(parent);}}else if (parent->_bf == -2)// 左子树高{if (cur->_bf == -1)// 左子树的左子树也高 --> 右单旋{RotateR(parent);}else if (cur->_bf == 1)// 左子树的右子树也高 --> 左右双旋{RotateLR(parent);}}break;}}return true;}
五、AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1. 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
2. 验证其为平衡树
每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
节点的平衡因子是否计算正确
int Height(Node* root){if (root == nullptr)return 0;int RightHeight = Height(root->_left);int LeftHeight = Height(root->_right);return RightHeight > LeftHeight ? RightHeight + 1 : LeftHeight + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;//空的话应该返回true,因为不影响平衡int LeftHeight = Height(root->_left);//迭代计算高度int RightHeight = Height(root->_right);if (RightHeight - LeftHeight != root->_bf)//仅仅判断高度是不够的,有可能平衡因子还是错了,所以要对每个平衡因子做检查{cout << "平衡因子现在是:" << root->_bf << endl;cout << "平衡因子应该是:" << (RightHeight - LeftHeight) << endl;return false;//平衡因子错了直接返回}return RightHeight - LeftHeight < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);}
六、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合
相关文章:
详解—[C++ 数据结构]—AVL树
目录 一.AVL树的概念 二、AVL树节点的定义 三、AVL树的插入 3.1插入方法 四、AVL树的旋转 1. 新节点插入较高左子树的左侧---左左:右单旋 2. 新节点插入较高右子树的右侧---右右:左单旋 3.新节点插入较高左子树的右侧---左右:先左单旋…...
卷积神经网络(CNN):乳腺癌识别.ipynb
文章目录 一、前言一、设置GPU二、导入数据1. 导入数据2. 检查数据3. 配置数据集4. 数据可视化 三、构建模型四、编译五、训练模型六、评估模型1. Accuracy与Loss图2. 混淆矩阵3. 各项指标评估 一、前言 我的环境: 语言环境:Python3.6.5编译器…...
有文件实体的后门无文件实体的后门rootkit后门
有文件实体后门和无文件实体后门&RootKit后门 什么是有文件的实体后门: 在传统的webshell当中,后门代码都是可以精确定位到某一个文件上去的,你可以rm删除它,可以鼠标右键操作它,它是有一个文件实体对象存在的。…...
GPT实战系列-大模型训练和预测,如何加速、降低显存
GPT实战系列-大模型训练和预测,如何加速、降低显存 不做特别处理,深度学习默认参数精度为浮点32位精度(FP32)。大模型参数庞大,10-1000B级别,如果不注意优化,既耗费大量的显卡资源,…...
SQL Sever 基础知识 - 数据排序
SQL Sever 基础知识 - 二 、数据排序 二 、对数据进行排序第1节 ORDER BY 子句简介第2节 ORDER BY 子句示例2.1 按一列升序对结果集进行排序2.2 按一列降序对结果集进行排序2.3 按多列对结果集排序2.4 按多列对结果集不同排序2.5 按不在选择列表中的列对结果集进行排序2.6 按表…...
vscode配置使用 cpplint
标题安装clang-format和cpplint sudo apt-get install clang-format sudo pip3 install cpplint标题以下settings.json文件放置xxx/Code/User目录 settings.json {"sync.forceDownload": false,"workbench.sideBar.location": "right","…...
C++ 系列 第四篇 C++ 数据类型上篇—基本类型
系列文章 C 系列 前篇 为什么学习C 及学习计划-CSDN博客 C 系列 第一篇 开发环境搭建(WSL 方向)-CSDN博客 C 系列 第二篇 你真的了解C吗?本篇带你走进C的世界-CSDN博客 C 系列 第三篇 C程序的基本结构-CSDN博客 前言 面向对象编程(OOP)的…...
C++ 指针详解
目录 一、指针概述 指针的定义 指针的大小 指针的解引用 野指针 指针未初始化 指针越界访问 指针运算 二级指针 指针与数组 二、字符指针 三、指针数组 四、数组指针 函数指针 函数指针数组 指向函数指针数组的指针 回调函数 指针与数组 一维数组 字符数组…...
.locked、locked1勒索病毒的最新威胁:如何恢复您的数据?
导言: 网络安全问题变得愈加严峻。.locked、locked1勒索病毒是近期备受关注的一种恶意软件,给用户的数据带来了巨大威胁。本文将深入探讨.locked、locked1勒索病毒的特征,探讨如何有效恢复被其加密的数据,并提供一些建议…...
Apache Sqoop使用
1. Sqoop介绍 Apache Sqoop 是在 Hadoop 生态体系和 RDBMS 体系之间传送数据的一种工具。 Sqoop 工作机制是将导入或导出命令翻译成 mapreduce 程序来实现。在翻译出的 mapreduce 中主要是对 inputformat 和 outputformat 进行定制。 Hadoop 生态系统包括:HDFS、Hi…...
【UGUI】实现UGUI背包系统的六个主要交互功能
在这篇教程中,我们将详细介绍如何在Unity中实现一个背包系统的六个主要功能:添加物品、删除物品、查看物品信息、排序物品、搜索物品和使用物品。让我们开始吧! 一、添加物品 首先,我们需要创建一个方法来添加新的物品到背包中。…...
电压驻波比
电压驻波比 关于IF端口的电压驻波比 一个信号变频后,从中频端口输出,它的输出跟输入是互异的。这个电压柱波比反映了它输出的能量有多少可以真正的输送到后端连接的器件或者设备。...
Open3D 最小二乘拟合二维直线(直接求解法)
目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重。 一、算法原理 平面直线的表达式为: y = k x + b...
面试题目总结(二)
1. IoC 和 AOP 的区别 控制反转(Ioc) 和面向切面编程(AOP) 是两个不同的概念,它们在软件设计中有着不同的应用和目的。 IoC 是一种基于对象组合的编程模式,通过将对象的创建、依赖关系和生命周期等管理权交给外部容器或框架来实现程序间的解耦。IoC 的…...
TrustZone概述
目录 一、概述 1.1 在开始之前 二、什么是TrustZone? 2.1 Armv8-M的TrustZone 2.2 Armv9-A Realm Management Ext...
[go 面试] Go Kit中读取原始HTTP请求体的方法
关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力! 在Go Kit中,如果你想读取未序列化的HTTP请求体,可以使用标准的net/http包来实现。以下是一个示例,演示了如何完成这个任务: package mainimport …...
小程序如何刷新当前页面?
在小程序中,刷新当前页面通常有两种方法: 使用 wx.navigateBack 方法: wx.navigateBack({delta: 1 }) 这将返回上一页,并刷新页面。你可以通过调整 delta 参数来控制返回的页面数。例如,如果你想要返回到两页之前的页…...
ChatGPT使用路径:从新手到专家的指南
原文&精华文章&转载注明:ChatGPT与日本首相交流核废水事件-精准Prompt... hello,我是小索奇,有任何问题或者需要帮助的都可以在这里找到我或者留言哈 一、初识ChatGPT 什么是ChatGPT? ChatGPT是一种大型语言模型&…...
VsCode 调试 MySQL 源码
1. 启动 MySQL 2. 查看 MySQL 进程号 [root ~]# ps -ef | grep mysqld root 21479 1 0 Nov01 ? 00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir/usr/local/mysql/data --pid-file/usr/local/mysql/data/mysqld.pid root 26622 21479 0 …...
Mysql中的正经行锁、间隙锁和临键锁
行锁、间隙锁和临键锁是数据库中的三种不同类型的锁,三者都属于行锁,第一个一般叫他正经的行锁(《Mysql是怎样运行的》一书中的说法)。 行锁(Row Lock):行锁是指对数据表中的某一行进行的锁定操…...
最强AI之风袭来,你爱了吗?
2017年,柯洁同阿尔法狗人机大战,AlphaGo以3比0大获全胜,一代英才泪洒当场...... 2019年,换脸哥视频“杨幂换朱茵”轰动全网,时至今日AI换脸仍热度只增不减; 2022年,ChatGPT一经发布便轰动全球&a…...
时间序列预测实战(二十三)进阶版LSTM多元和单元预测(课程设计毕业设计首选)
一、本文介绍 本篇文章给大家带来的是利用我个人编写的架构进行LSTM模型进行时间序列建模(专门为了时间序列领域新人编写的架构,简单且不同于市面上大家用GPT写的代码),包括结果可视化、支持单元预测、多元预测、模型拟合效果检测…...
Python之Appium 2自动化测试(Android篇)
一、环境搭建及准备工作 1、Appium 2 环境搭建 请参考另一篇文章: Windows系统搭建Appium 2 和 Appium Inspector 环境 2、安装 Appium-Python-Client,版本要求3.0及以上 pip install Appium-Python-ClientVersion: 3.1.03、手机连接电脑,并在dos窗口…...
chromium通信系统-ipcz系统(四)-ipcz-分层、和mojo的关系以及handle
在只有mojo的情况下, 进程间通信都是靠unix 域套接字来完成了,由于这种方式比较低效,并且不够灵活,后来引入了ipcz。 但是系统中基本上使用mojo做进程间通信,想要一步到位迁移到ipcz系统是比较困难的。 所以chrome团队…...
推荐一些研发人员经常用到的免费API接口
快递物流订阅与推送(含物流轨迹):【物流订阅与推送、H5物流轨迹、单号识别】支持单号的订阅与推送,订阅国内物流信息,当信息有变化时,推送到您的回调地址。地图轨迹支持在地图中展示包裹运输轨迹。包括顺丰…...
高薪资是跳出来的,好工作是面出来的~
听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 如需要项目实战或者是体系化资源,文末名片加V! 作者:哈哥撩编程,工作十余年, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。荣誉:2022年度博客之星Top4、2023年度超级个体得主、谷歌与亚马逊开发…...
记QListWidget中QPushButton QSS样式失效的“bug”
一、场景 有一个QListWidget的列表;里面存放了若干QListWidgetItem;每个QListWidgetItem与一个自定义类对象绑定——通过QListWidget的setItemWidget()实现。自定义对象继承于QWidget,且内含QPushButton。 二、bug描述 在该QListWidget的外…...
python提取通话记录中的时间信息
您需要安装适合中文的SpaCy模型。您可以通过运行 pip install spacypython -m spacy download zh_core_web_sm来安装和下载所需的模型。 import spacy# 加载中文模型 nlp spacy.load(zh_core_web_sm)# 示例电话记录文本 text """ Agent: 今天我们解决一下这…...
DSShop移动商城网店系统 反序列化RCE漏洞复现
0x01 产品简介 DSShop是长沙德尚网络科技有限公司推出的一款单店铺移动商城网店系统,能够帮助企业和个人快速构建手机移动商城,并减少二次开发带来的成本。 以其丰富的营销功能,精细化的用户运营,解决电商引流、推广难题,帮助企业打造生态级B2C盈利模式商业平台。完备的电商…...
docker搭建node环境开发服务器
docker搭建node环境开发服务器 本文章是我自己搭建node环境开发服务器的过程记录,不一定完全适用所有人。根据个人情况,按需取用。 命名项目路径 为了方便cd到项目路径,将项目路径重命名,方便输入。 vim /etc/profile # 修改p…...
怎么做点击图片进网站/注册域名在哪里注册
一、OSPF的基本概念和工作过程 1、OSPF路由协议概述 自治系统(AS),划分自治系统的原因:收敛时间 内部网关协议(IGP)——RIP、OSPF 外部网关协议(EGP)——BGP OSPF工作过程: 建立邻接关系—…...
徐州旅游的网站建设/有没有永久免费crm
今天突然想在windows下利用cygwin执行一个脚本判断输入的文件名是目录还是文件,代码很简单,如下 #!/bin/shread -p enter file name: filenamepath$filenameif [ -d $path ] then echo $filename is the directoryelif [ -f $path ] then echo $path is …...
微信如何做模板下载网站/百度快照搜索引擎
说明: 有时候服务器是内网服务器,无法连接互联网,即无法使用互联网的yum源,这是如果安装salt的话会有一点麻烦,下面说下我是怎么做的。 第一步:使用虚拟机或者可以联网的服务器安装一遍salt,安装…...
做临床研究在哪个网站注册/网络营销成功案例ppt免费
大家应该都被这个问题给困扰过,打开电脑,电脑上全部都是广告弹窗,或者在办公、追剧的时候,电脑突然就弹出了一条烦人的广告,今天就教大家4个方法,永久关闭这些广告弹窗。方法一1、按下组合键【winr】打开运…...
建设部网站有建筑施工分包/商品seo优化是什么意思
原文:http://coolketang.com/staticOffice/5a97f1019f54542163dc2f49.html 1. 本节课将为您演示,如何给当前的工作表添加背景图片,以增加工作表的趣味性和亲和力。首先点击[页面布局]选项卡,显示页面布局功能面板。 2. 在页面设置…...
南京企业做网站/营销100个引流方案
第二题生日蜡烛(结果填空)某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。现在算起来,他一共吹熄了236根蜡烛。请问,他从多少岁开始过生日party的?请填写他开始过生日party的年龄数。注意…...