怎么做虚拟币网站/安徽网站关键字优化
文章目录
- 1.AVL树
- 1.1 AVL树的概念
- 1.2 AVL树节点的定义
- 1.3 AVL树的插入
- 1.4 AVL树的旋转
- 1.4.1 左单旋
- 1.4.2 右单旋
- 1.4.3 右左双旋
- 1.4.4 左右双旋
- 1.5 AVL树的平衡验证
- 1.6 AVL树的删除
- 1.7 AVL树的性能
1.AVL树
在前面,我们已经介绍过了二叉搜索树,也了解到二叉搜索树查找的效率非常的高。但是在数据有序或接近有序时,它就会退化成单边树,那样效率就变得非常低了。所以我们也一直说二叉搜索树的搜索的时间复杂度是O(N)(高度次),并不是O(log2N)。
因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。
1.1 AVL树的概念
由于二叉搜索树可能会退化,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
为什么要求左右子树的高度之差不超过1就可以呢?
对于所有形状的子树来说,如果他有2n-1个节点,那它就可以保持绝对的平衡;如果他有2n (n >= 1)个节点,就无法做到绝对的平衡,最好的结果就是子树高度相差一。
一棵AVL树要么是空树,要么是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果他有n个节点,其高度可保持在log2N,搜索的时间复杂度就是log2N。
1.2 AVL树节点的定义
为了方便确认一棵树是否是AVL树,我们在节点中定义一个平衡因子,通过它来记录每棵树左右子树的高度。
如果右子树比左子树高一层,那平衡因子就是1;如果左右子树一样高,那平衡因子就是0;如果左子树比右子树高一层,那平衡因子就是-1。此时AVL树的性质都没有被打破。
当左子树比右子树高两层,那平衡因子就是-2;或者右子树比左子树高两层,平衡因子就是2,此时AAVL树的性质就被打破了,需要调整。
在调整失衡的AVL树时,我们需要频繁的访问其父节点,因此我们给每个节点定义一个指向父亲的指针
template<class K, class V>
struct AVLTreeNode
{pair<K, V> _kv; //存放数据AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _prev; //指向当前节点的父亲int _bf;//平衡因子AVLTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),prev(nullptr),_bf(0){}
};
1.3 AVL树的插入
AVL树的插入与普通二叉搜索树的插入基本一致。唯一的区别就是AVL树插入节点后需要判断AVL树是否失衡,存在失衡就需要调整。
我们平衡因子是使用右子树的高度-左子树的高度,因此,如果新节点插入到父节点的右边,那父亲的平衡因子+1;如果新节点插入到父亲节点的左边,那父亲的平衡因子-1。
对于情况二
来说,新节点的插入不会影响其祖先的平衡因子的改变
,因为子树的高度没有改变。
对于情况一来说,新节点的插入会影响其祖先的平衡因子的改变,因为子树的高度变高了。
所以,对于情况一来说,我们新插入节点后,还要观察其祖先的平衡因子是否变化,变化后是否需要调整。
那么下面我们就先将节点插入到树中:
bool insert(const pair<K, V>& kv){//插入前是空树if (_root == nullptr){//新插入的节点就是根_root = new Node(kv);_root->_prev = nullptr;return true;}else{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;//节点已经存在,插入失败}}//已找到了插入位置cur = new Node(kv);//判断连接在父亲的哪一侧if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_prev = parent;//指向父亲//判断树是否失衡//...}}
更新父亲/祖先的平衡因子
//判断树是否失衡while (parent){//更新父亲的平衡因子if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)//属于情况二,不影响祖先break;//情况一,影响祖先的平衡因子,需要继续更新else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = cur->_prev;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转处理break;}else{assert(false);//平衡因子异常了}}
1.4 AVL树的旋转
AVL树的旋转分为四种情况:
- 左单旋
- 右单旋
- 左右双旋
- 右左双旋
下面,我们对这四种情况具体分析
1.4.1 左单旋
新节点插入到较高右子树的右侧- -左单旋
在旋转过程中,有以下几种情况需要考虑:
- 7节点的左孩子可能存在,也可能不存在
- 6可能是根节点,也可能是子树
- 如果是根节点,旋转完成后,根节点就是7
- 如果是子树,可能是某个节点的左子树,也可能是右子树,需要判断以后连接
void RotateL(Node* parent){Node* subR = parent->_right;//父亲的右孩子Node* subRL = subR->_left;//右孩子的左孩子//升高度,连孙子parent->_right = subRL;if (subRL)subRL->_prev = parent;Node* parentPrev = parent->_prev;//降高度,变父亲subR->_left = parent;parent->_prev = subR;//连接旋转后的子树subR->_prev = parentPrev;if (parentPrev == nullptr){//如果原父亲就是根,旋转后的儿子变根_root = subR;}else{//原父亲是子树,判断儿子连接在哪一边if (parentPrev->_left == parent)parentPrev->_left = subR;elseparentPrev->_right = subR;}//更新平衡因子subR->_bf = 0;parent->_bf = 0;}
1.4.2 右单旋
新节点插入到较高左子树的左侧- - 右旋
在旋转过程中,有以下几种情况需要考虑:
- 5节点的右孩子可能存在,也可能不存在
- 6可能是根节点,也可能是子树
- 如果是根节点,旋转完成后,根节点就是5
- 如果是子树,可能是某个节点的左子树,也可能是右子树,需要判断以后连接
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;//升高度,连孙子parent->_left = subLR;if (subLR)subLR->_prev = parent;Node* parentPrev = parent->_prev;//降高度,变父亲subL->_right = parent;parent->_prev = subL;//新父亲指向前subL->_prev = parentPrev;if (nullptr == parentPrev){//如果原父亲就是根,旋转后的儿子变根_root = subL;}else{//原父亲是子树,判断儿子连接在哪一边if (parentPrev->_left == parent)parentPrev->_left = subL;elseparentPrev->_right = subL;}subL->_bf = 0;parent->_bf = 0;}
1.4.3 右左双旋
新节点插入到较高右子树的左侧- -右左双旋
所以,为了解决这种情况,我们可以将这种歪脖树先变成一棵单边树,然后再进行单旋即可。
为了满足所有情况,我们下面使用抽象图再画一遍
观察上图我们可以发现,双旋就是一种抛弃自己的孩子,独自登基的感觉,因此对于旋转后平衡因子的改变,取决于新节点插入到哪一边,最后又到哪里去。
void RotateRL(Node* parent){//由于单旋会改变节点的位置以及平衡因子,所以提前记录Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);//修改平衡因子if (bf == 0)//subRL插入前是空{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1)//subRL之前是一棵树,新节点插入其 右侧{parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else //subRL之前是一棵树,新节点插入其 左侧{parent->_bf = 0;subR->_bf = -1;subRL->_bf = 0;}}
1.4.4 左右双旋
新节点插入到较高左子树的右侧–左右双旋
最简单的情况,2的右子树插入前为空
抽象图
跟右左双旋基本一样,这里就不赘述了,咱们直接开旋。
平衡因子的改变也需要小心。
void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0) // subLR插入前为空{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1) //subLR是一棵树,新节点插入到树的 右侧{parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else //subLR是一棵树,新节点插入到树的 左侧{parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}}
下面就是更新平衡因子的整体思路:
//判断树是否失衡while (parent){//更新父亲的平衡因子if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)//属于情况二,不影响祖先break;//情况一,影响祖先的平衡因子,需要继续更新else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = cur->_prev;}else if (parent->_bf == 2 || parent->_bf == -2){//旋转处理//左单旋// p cur// cur ---> p new// newif (parent->_bf == 2 && cur->_bf == 1)RotateL(parent);//右单旋// p cur// cur ---> new p// newelse if (parent->_bf == -2 && cur->_bf == -1)RotateR(parent);//右左双旋// p p cur// cur --> cur --> p new// new newelse if (parent->_bf == 2 && cur->_bf == -1)RotateRL(parent);//左右双旋// p p cur// cur --> cur --> new p // new newelse if (parent->_bf == -2 && cur->_bf == 1)RotateLR(parent);elseassert(false);break;//旋转后,以parent为根的树已经平衡,无需继续向上更新}else{assert(false);//平衡因子异常了}
总结:
假如以Parent为根的子树不平衡,即Parent的平衡因子为2或者-2,分以下情况考虑
- Parent的平衡因子为2,说明Parent的右子树高,设Parent的右子树的根为SubR
- 当SubR的平衡因子为1时,执行左单旋
- 当SubR的平衡因子为-1时,执行右左双旋
- Parent的平衡因子为-2,说明Parent的左子树高,设Parent的左子树的根为SubL
- 当SubL的平衡因子为-1是,执行右单旋
- 当SubL的平衡因子为1时,执行左右双旋
旋转完成后,原Parent为根的子树个高度降低,已经平衡,不需要再向上更新。
1.5 AVL树的平衡验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
- 验证其为二叉搜索树
- 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
- 验证其为平衡树
- 每个节点子树高度差的绝对值不能超过1
- 节点的平衡因子是否计算正确
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;}bool _isBalanceTree(){return isBalanceTree(_root);}bool isBalanceTree(Node* root){if (root == nullptr)//空树也是平衡二叉搜索树return true;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);int gap = rightHeight - leftHeight; //计算root的平衡因子// 如果计算出的平衡因子与root的平衡因子不相等,或者//root平衡因子的绝对值超过1,则一定不是AVL树if (gap != root->_bf || (gap > 1 || gap < -1)){return false;}return isBalanceTree(root->_left) && isBalanceTree(root->_right);}
1.6 AVL树的删除
- 因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除
- (分析有无左右孩子,若仅有一个孩子,则直接将删除节点连接孩子;若有两个孩子,将删除节点替换为删除节点的左子树的最大或右子树的最小)
- 然后再更新平衡因子,只不过与插入不同的是,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。
1.7 AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2N。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
相关文章:

【数据结构】AVL树(平衡二叉搜索树)
文章目录 1.AVL树1.1 AVL树的概念1.2 AVL树节点的定义1.3 AVL树的插入1.4 AVL树的旋转1.4.1 左单旋1.4.2 右单旋1.4.3 右左双旋1.4.4 左右双旋 1.5 AVL树的平衡验证1.6 AVL树的删除1.7 AVL树的性能 1.AVL树 在前面,我们已经介绍过了二叉搜索树,也了解到…...

ASP.NET Web Api 使用 EF 6,DateTime 字段如何取数据库服务器当前时间
前言 在做数据库设计时,为了方便进行数据追踪,通常会有几个字段是每个表都有的,比如创建时间、创建人、更新时间、更新人、备注等,在存储这些时间时,要么存储 WEB 服务器的时间,要么存储数据库服务器的时间…...

【HarmonyOS】应用设置屏幕常亮
【HarmonyOS】应用设置屏幕常亮 一、问题背景: 金融类或钱包场景的应用APP,对于付款码,扫一扫等场景都会对屏幕设置常亮。防止屏幕长时间不操作,自动息屏。 目前这种场景的需求也是非常有必要的,也是行业内默认的处理…...

Docker部署Elasticsearch8.6.0 Kibana8.6.0
(1)Docker部署Elasticsearch8.5.3(失败…) 为了匹配springboot3.0.x,安装Elasticsearch:8.5.3 拉取镜像,遇到问题! [rootserver01 ~]# docker pull elasticsearch:8.5.3 8.5.3: Pulling from…...

第四篇论文小记
一、第一次投稿 期刊:《Remote Sensing》 研究方向:人工智能应用 投稿结果:已投被拒 投稿周期:3天 最后更新时间:19 July 2024 投稿流程: 状态时间Pending review16 July 2024Reject by editor19 July …...

python使用 tkinter 生成随机颜色
先看效果: 只要不停点击底部的按钮,每次都会生成新的颜色。炫酷啊。 import random import tkinter import tkinter.messagebox from tkinter import Button# todo """ 1. 设置一个按钮,来让用户选择是否显示颜色值 2. 把按钮换成 Label…...

【Linux学习 | 第1篇】Linux介绍+安装
文章目录 Linux1. Linux简介1.1 不同操作系统1.2 Linux系统版本 2. Linux安装2.1 安装方式2.2 网卡设置2.3 安装SSH连接工具2.4 Linux和Windows目录结构对比 Linux 1. Linux简介 1.1 不同操作系统 桌面操作系统 Windows (用户数量最多)MacOS ( 操作体验好,办公人…...

设计模式-抽象工厂
抽象工厂属于创建型模式。 抽象工厂和工厂设计模式的区别: 工厂模式的是设计模式中最简单的一种设计模式,主要设计思想是,分离对象的创建和使用,在Java中,如果需要使用一个对象时,需要new Class()ÿ…...

Ubunton-24.04 简单配置使用
目录 1.设置 root 密码 2. 防火墙设置 1. 安装防火墙 2. 开启和关闭防火墙 3. 开放端口和服务规则 4. 关闭端口和删除服务规则 5 查看防火墙状态 3. 设置网络 1.设置 root 密码 1. 切换到 root 用户,并输入当前登录账号的密码 sudo -i 2. 设置新密码…...

什么是STP环路保护
在运行生成树协议的网络中,根端口和其他阻塞端口状态是依靠不断接收来自上游设备的BPDU维持。当由于链路拥塞或者单向链路故障导致这些端口收不到来自上游交换设备的BPDU时,设备会重新选择根端口。原先的根端口会转变为指定端口,而原先的阻塞…...

Python算法基础:解锁冒泡排序与选择排序的奥秘
在数据处理和算法设计中,排序是一项基础且重要的操作。本文将介绍两种经典的排序算法:冒泡排序(Bubble Sort)和选择排序(Selection Sort)。我们将通过示例代码来演示这两种算法如何对列表进行升序排列。 一…...

QtCMake工程提升类后找不到头文件
链接: QtCMake工程提升类后找不到头文件_qt提升类找不到头文件-CSDN博客 重点: 1.原因:出现问题的原因是Qt creator通过ui文件生成的程序和存放头文件的目录不在一起,但是生成的程序里会在生成目录下找头文件,所以肯…...

Docker核心技术:Docker原理之Cgroups
云原生学习路线导航页(持续更新中) 本文是 Docker核心技术 系列文章:Docker原理之Cgroups,其他文章快捷链接如下: 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术:…...

union的特性和大小端
一、union在c和c语言中的特性 1.共享内存空间:union的所有成员共享同一块内存空间。意味着在同一时刻,union 只能存储其成员 中的一个值。当你修改了union中的一个成员,那么其它成员的值也会被改变,因为它们实际上都是指向同一块…...

个性化IT服务探索实践
探索和实践个性化IT服务,可以为用户提供更优质、定制化的解决方案,从而提升用户体验和满意度。以下是一些具体的步骤和建议,帮助自己在未来探索和实践个性化IT服务。 一、了解用户需求 用户调研和反馈: 进行用户调研,了解用户的需求和痛点。收集用户反馈,通过问卷、采访…...

UE4-打包游戏,游戏模式,默认关卡
一.打包游戏 注意windows系统无法打包苹果系统的执行包,只能使用苹果系统打包。 打包完之后是一个.exe文件。 打包要点: 1.确定好要操控的角色和生成位置。 2.设置默认加载的关卡和游戏模式。 在这个界面可以配置游戏的默认地图和游戏的模式,…...

Unity ShaderLab基础
[原文1] [参考2] 一 基础知识 1. 1 着色器语言分类: 语言说明HLSL基于 OpenGL 的 OpenGL Shading LanguageGLSL基于 DirectX 的 High Level Shading LanguageCGNVIDIA 公司的 C for GraphicShader LabUnity封装了CG,HLSL,GLSL的Unity专用着色器语言,具有跨平台,图形化编程,便…...

用代理IP会频繁掉线是什么原因?HTTP和SOCKS5协议优劣势是什么?
在使用代理IP的过程中,频繁掉线是一个常见且令人头痛的问题。要解决这一问题,我们需要先了解其原因,然后比较HTTP和SOCKS5两种代理协议的优劣势,以选择最适合的解决方案。 一、代理IP频繁掉线的原因 1. 代理服务器稳定性 代理服…...

MongoDB教程(十三):MongoDB覆盖索引
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言什么是覆盖…...

快速认识EA(Enterprise Architecture)
前言 企业架构,英文是:Enterprise Architecture,简称:EA,是承接企业战略规划与IT建设之间的桥梁,是企业信息化的核心,主要包括业务架构和IT架构。 架构的本质是管理和解决系统的复杂性&#x…...

词云图制作
词云图制作 一、什么是词云 这就是词云。 “词云”的概念最早是美国西北大学新闻学副教授、新媒体专业主任里奇•戈登( Rich Gordon )提出的。词云( Word Cloud ),又称文字云、标签云( Tag Cloud &#x…...

AndroidStudio与手机进行无线调试
(一)、前提条件 一部手机一条USB数据线一部电脑手机和电脑连接到同一个 Wifi开启手机的USB调试功能开启手机的无线调试功能 (二)、操作步骤 1、 将手机和电脑用USB数据线连接 2、 打开 终端,输入 adb devices ,查看手机和电脑是否连接成功。如下图: 2、…...

脉冲编码调制(PCM,Pulse Code Modulation)简介
脉冲编码调制(PCM,Pulse Code Modulation) 脉冲编码调制(PCM,Pulse Code Modulation)是一种将模拟信号转换为数字信号的技术。在音频处理、电话通信以及其他许多领域都有广泛应用。PCM通过采样、量化、编码等三个主要步骤将模拟信号转换为数…...

Pytorch transforms 的研究
绝对路径与相对路径差别 transforms的使用 from torchvision import transforms from PIL import Imageimg_path "dataset/train/bees/16838648_415acd9e3f.jpg" img Image.open(img_path) tensor_trans transforms.ToTensor() tensor_img tensor_trans(img) prin…...

一个C++模板工厂的编译问题的解决。针对第三方库的构造函数以及追加了的对象构造函数。牵扯到重载、特化等
一窥模板的替换和匹配方式:偏特化的参数比泛化版本的还要多:判断是不是std::pair<,>。_stdpair模板参数太多-CSDN博客 简介 在一个项目里,调用了第三封的库,这个库里面有个类用的很多,而且其构…...

《昇思 25 天学习打卡营第 20 天 | Pix2Pix实现图像转换 》
《昇思 25 天学习打卡营第 20 天 | Pix2Pix实现图像转换 》 活动地址:https://xihe.mindspore.cn/events/mindspore-training-camp 签名:Sam9029 Pix2Pix模型概述 Pix2Pix是一种基于条件生成对抗网络(cGAN)的图像转换模型&#x…...

关于c#的简单应用三题
#region 输入一个正整数,求1~这个数的阶乘 public static void Factorial(int a) { int result 1; for (int i 1; i < a; i) { result result * i; } Console.WriteLine(result); } #endregion #region 一个游戏&#…...

(十三)Spring教程——依赖注入之工厂方法注入
1.工厂方法注入 工厂方法是在应用中被经常使用的设计模式,它也是控制反转和单例设计思想的主要实现方法。由于Spring IoC容器以框架的方式提供工厂方法的功能,并以透明的方式开放给开发者,所以很少需要手工编写基于工厂方法的类。正是因为工厂…...

Redission中的Lua脚本写法、理解
对于Redission看门狗机制中的为了保证原子性的Lua脚本的写法规则是什么样的呢 ? 对于源码中的Lua脚本又是什么意思? 我们一起来看一下 首先,我们先基本的熟悉一下lua脚本的逻辑 在Lua脚本中,if (…) then … end 语句的执行过程…...

视频共享融合赋能平台LntonCVS视频监控管理平台视频云解决方案
LntonCVS是基于国家标准GB28181协议开发的视频监控与云服务平台,支持多设备同时接入。该平台能够处理和分发多种视频流格式,包括RTSP、RTMP、FLV、HLS和WebRTC。主要功能包括视频直播监控、云端录像与存储、检索回放、智能告警、语音对讲和平台级联&…...