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

数据结构:AVL树

前言

学习了普通二叉树,发现普通二叉树作用不大,于是我们学习了搜索二叉树,给二叉树新增了搜索、排序、去重等特性,
但是,在极端情况下搜索二叉树会退化成单边树,搜索的时间复杂度达到了O(N),这是十分不利的,
所以,牛人们又提出了新的数据结构:AVL树(平衡搜索二叉树),给搜索二叉树新增了平衡的特性,控制左右子树的高度,是搜索二叉树处于平衡的状态,避免出现极端情况。

AVL树的发明者是两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis,为了几年他们在1962年提出该数据结构,就命名为AVL树。


AVL树的特性

AVL树要求任意节点的左右子树的高度差的绝对值不超过1.
为什么拥有该特性AVL树就可以保持平衡呢?这里需要数学证明,就不解释了,理解原理后,就很容易理解。

一棵AVL树是具有以下性质的二叉搜索树:

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

在这里插入图片描述
注意:在这里,我们引入一个变量,平衡因子(balance factor),用来记录左右子树的高度差,
这里我们记录右子树的高度减左子树的高度。
当然,也可以有其他的思路来替代平衡因子。


AVL树节点的定义(以KV型为例)

template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K,V>* _left = nullptr;//左子节点AVLTreeNode<K, V>* _right = nullptr;//右子节点AVLTreeNode<K, V>* _parent = nullptr;//父亲节点pair<K, V> _kv;//数据int _bf = 0;//平衡因子AVLTreeNode(const pair<K, V>& kv):_kv(kv){}
};

注意:AVL树使用三叉链实现,多了一个parent指针,用来记录父亲节点,方便使用。


AVL树的插入(核心)

AVL树是在搜索二叉树上面进行升级,所以查找的方法和搜索二叉树类似,大了就向右子树走,小了就往左子树走。

因为这里多了parent指针和bf,在插入之后都需要进行维护。(天下没有白吃的午餐,使用的时候方便,维护起来就麻烦了QAQ)

parent指针处理起来很简单,我们这里主要讲bf如何控制。

bf有三种取值 0 , - 1 , 1
当我们在节点的右侧插入的时候,节点的bf++,在节点的左侧插入的时候,节点的bf–。

在右边插入,右子树的高度增加,bf++,在左边插入的时候,左子树的高度增加,bf–

这是毫无疑问的,以下都基于此。

OK,更新完当前节点之后,bf就维护好了吗?
哪有这么简单QAQ

在这里插入图片描述
在插入后cur的bf更新为1,仅仅如此吗?
看这张图,parent的bf也要从1变成2了。

所以,在更新完cur节点的bf后,还需要根据情况确定是否要继续向上更新

具体是否要更新,主要是看子树的高度有没有发生变化

有哪些情况呢?

  1. cur插入后更新为0
    说明原来是1 或者 -1,在较短的那一条边上新增了新节点,将cur节点变平衡了,
    这样的话,那高度就没有增加,不会影响到子树的高度,就不需要向上更新bf了。
  2. cur插入后更新为 1,-1
    说明原来是0,原来是平衡的,插入新节点后破坏了平衡,子树高度发生了变化,
    所以需要向上更新bf。
  3. cur插入后更新为 2,-2
    当更新为2,-2后,发现违反了AVL树的规则,不再是一颗AVL树,我们就需要进
    行特殊操作来维护AVL树的性质了。(这里,我们采用旋转

AVL树的旋转(最难的部分)

AVL树的旋转是AVL树这个数据结构的亮点,掌握了这一点之后,才会理解这种数据结构有多么精妙。

从深层上看,AVL树的旋转分为四种情况,我们画图来分析。(由于实际情况太多太多,我们这里画抽象图)

左单旋

在这里插入图片描述

这里h >= 0
我们在最右边插入一个节点,使AVL树的右侧完全倾斜,那么我们就需要向左侧旋转了。
如何向左旋转呢?
在这里插入图片描述

我们将三个需要用到的节点命名一下,分别为parent,subR,以及subRL
根据搜索二叉树的性质,我们知道subRL > parent ,但是小于subR,所以就可以进行左单旋而不会破坏搜索的性质

左单旋就是
先将subRL插入到parent的右边
接着将parent插入到subR的左边

成功旋转之后图形变成下面的样子
在这里插入图片描述
这样左右子树的高度就一样了,重新将AVL树调整平衡了
这里还有非常多的代码细节,例如空指针,parent指针的维护等等需要处理,这里大家可以先尝试根据思路写出代码,再跟后面的代码进行比较,看看是否写对了。

这里只简单讲一下平衡因子的维护。
可以看到,在左单旋只会影响parent和subR两个节点的bf,且旋转后皆为0,故不用向上继续调整。


右单旋

右单旋和左单旋类似,是对称的,这里只简单画出抽象图,相信读者能够自己理解。
在这里插入图片描述
右单旋就是处理左边完全倾斜的情况,向右边旋转,进而调整平衡。
代码依旧在文末尾给出。


右左双旋

顾名思义,先右单旋后左单旋构成右左双旋。
相信聪明的小伙伴,在看到左单旋和右单旋的时候,就会发现这两种情况都是插入在最右边和最左边造成完全倾斜。
而插入在右子树的左边和左子树的右边都没有列举出来。

这里右左双旋就是应对插入在右子树的左边这种情况的,显然,左右双旋就是应对插入在左子树的右边那种情况的,这里讲右左双旋,左右双旋留给大家自己思考。

在这里插入图片描述
左单旋和右单旋都不适合这种情况,根本原因在于,这种情况并不是完全倾斜,很简单嘛,
你不是完全倾斜,我强行让你变成完全倾斜,不就可以使用左单旋或者右单旋了嘛。

在这里插入图片描述
对于这种情况,我们可以看到是subRL太高了,导致的不平衡,我们将subRL旋转到subR的位置岂不是可以造成完全倾斜了吗?

这右单旋不就来了嘛,使用右单旋就将subRL的右插入到subR的左,再将subR插入到subRL的右即可。

右单旋完了之后,就是下面的图形
在这里插入图片描述
映入眼帘的就是右边太高了,出现了完全倾斜,直接左单旋调整平衡即可。

这里同样有很多代码细节,要维护好parent和bf,以及处理好空指针的特殊情况。

这里还是只简单讲一下bf的维护
我们以终为始,只看旋转前和旋转后的两幅图。
在这里插入图片描述

我们可以看到只有三个节点60 30 90的bf发生了变化。
也就是只有parent,subR,subRL三个节点的bf发生了变化。
这里最后subRL和subR的bf 变成了0,parent的bf变成了-1。
但是,一定是这样吗?
这中情况是在c子树上插入新节点,如果在b上插入新节点。
a和b的高度都是h,parent的bf就是0,c的高度是h-1,d的高度是h,subR的bf就是1

问题就在于是在subRL的左子树插入还是右子树插入新节点。
如何分辨?
很简单,去观察subRL的旋转前的bf
如果是1,就是在右子树插入,如果是-1,就是在左子树插入


左右双旋

大体和右左双旋类似,所以这里简单画个抽象图,相信读者能够自行理解。
在这里插入图片描述

这里就是新节点插入在了左子树的右边,导致不平衡,先左单旋调整成完全倾斜,在右单旋调整至平衡。


代码

#include <assert.h>
#include <iostream>using namespace std;namespace Avltree//命名空间名不能和类名相同,不然会发生命名冲突
{template<class K, class V>struct AVLTreeNode{AVLTreeNode<K,V>* _left = nullptr;AVLTreeNode<K, V>* _right = nullptr;AVLTreeNode<K, V>* _parent = nullptr;pair<K, V> _kv;int _bf = 0;//平衡因子AVLTreeNode(const pair<K, V>& kv):_kv(kv){}};template<class K,class V>class AVLTree{typedef AVLTreeNode<K, V> Node;private:Node* _root = nullptr;//开始的时候要给空,不然就是野指针了void RotateL(Node* parent)//左单旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;if (pparent == nullptr){subR->_parent = nullptr;_root = subR;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}parent->_bf = 0;subR->_bf = 0;}void RotateR(Node* parent)//右单旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;if (pparent == nullptr){subL->_parent = nullptr;_root = subL;}else{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}subL->_parent = pparent;}parent->_bf = 0;subL->_bf = 0;}void RotateRL(Node* parent){RotateR(parent->_right);RotateL(parent);}void RotateLR(Node* parent){RotateL(parent->_left);RotateR(parent);}void InOrder(Node* root){if (root == nullptr)return;InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;InOrder(root->_right);}public:void inorder(){InOrder(_root);}bool find(const K& key){Node* cur = _root;while (cur){if (key > cur->_kv.first){cur = cur->_right;}else if (key < cur->_kv.first){cur = cur->_left;}else{return true;}}return false;}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 (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;//如果已经在,那就插入失败,返回false}}//找到了,开始插入;cur = new Node(kv);cur->_parent = parent;if (kv.first > parent->_kv.first)//插入的节点很大,插在右边{parent->_bf++;parent->_right = cur;}else{parent->_bf--;parent->_left = cur;}//插入完成,调整平衡因子;while (parent){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)//右边太高了,左单旋{RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1)//左边太高了,右单旋{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == -1)//右边高,但是偏了{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateRL(parent);if (bf == 0){parent->_bf = subR->_bf = subRL->_bf = 0;}else if (bf == 1){subRL->_bf = subR->_bf = 0;parent->_bf = -1;}else if(bf == -1){subRL->_bf = parent->_bf = 0;subR->_bf = 1;}else{assert(false);}}else if (parent->_bf == -2 && cur->_bf == 1)//左边高但是偏向右边{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateLR(parent);if (bf == 0){parent->_bf = subL->_bf = subLR->_bf = 0;}else if (bf == 1){subL->_bf = -1;parent->_bf = subLR->_bf = 0;}else if(bf == -1){subLR->_bf = subL->_bf = 0;parent->_bf = 1;}else{assert(false);}}}else{assert(false);//走到这里来就出现错误了}}return true;}};
}

对于AVL树的删除,和插入类似,但是更加复杂些,限于篇幅,就暂且不讲AVL树的删除了,感兴趣的读者可以参考《算法导论》和《数据结构(用面向对象方法与C++语言描述)》(殷人昆版)。


相关文章:

数据结构:AVL树

前言 学习了普通二叉树&#xff0c;发现普通二叉树作用不大&#xff0c;于是我们学习了搜索二叉树&#xff0c;给二叉树新增了搜索、排序、去重等特性&#xff0c; 但是&#xff0c;在极端情况下搜索二叉树会退化成单边树&#xff0c;搜索的时间复杂度达到了O(N)&#xff0c;这…...

系统守护者:使用PyCharm与Python实现关键硬件状态的实时监控

目录 前言 系统准备 软件下载与安装 安装相关库 程序准备 主体程序 更改后的程序&#xff1a; 编写.NET程序 前言 在现代生活中&#xff0c;电脑作为核心工具&#xff0c;其性能和稳定性的维护至关重要。为确保电脑高效运行&#xff0c;我们不仅需关注软件优化&#xf…...

【工作流引擎集成】springboot+Vue+activiti+mysql带工作流集成系统,直接用于业务开发,流程设计,工作流审批,会签

前言 activiti工作流引擎项目&#xff0c;企业erp、oa、hr、crm等企事业办公系统轻松落地&#xff0c;一套完整并且实际运用在多套项目中的案例&#xff0c;满足日常业务流程审批需求。 一、项目形式 springbootvueactiviti集成了activiti在线编辑器&#xff0c;流行的前后端…...

SumatraPDF一打开就无响应怎么办?

结论&#xff1a;当前安装版不论32位还是64位都会出现问题。使用portable免安装版未发现相关问题。——sumatrapdf可以用于pdf, epub, mobi 等格式文件的浏览。 点击看相关问题和讨论...

棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程

一、前言 【试用版软件下载&#xff0c;可以点击本文章最下方官网卡片】 棋牌灯控计时计费系统软件免费试用版怎么下载 佳易王计时收银管理系统操作教程 棋牌计时计费软件的应用也提升了顾客的服务体验&#xff0c;顾客可以清晰的看到自己的消费时间和费用。增加了消费的透明…...

Excel下拉菜单制作及选项修改

Excel下拉菜单 1、下拉菜单制作2、下拉菜单修改 下拉框&#xff08;选项菜单&#xff09;是十分常见的功能。Excel支持下拉框制作&#xff0c;通过预设选项进行菜单选择&#xff0c;可以避免手动输入错误和重复工作&#xff0c;提升数据输入的准确性和效率 1、下拉菜单制作 步…...

树莓派 mysql (兼容mariadb)登陆问题

树莓派 mysql &#xff08;兼容mariadb&#xff09;登陆问题 树莓派 MySQL 登陆问题 1 使用默认账号登陆 在首次登陆的情况下&#xff0c;系统默认为root用户授权 sudo su root ![切换到root 用户](https://img-blog.csdnimg.cn/20191019082911668.png) 2. 使用root用户登…...

智能手表(Smart Watch)项目

文章目录 前言一、智能手表&#xff08;Smart Watch&#xff09;简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…...

设计模式~~~

简单工厂模式(静态工厂模式) 工厂方法模式 抽象工厂角色 具体工厂角色...

Golang | Leetcode Golang题解之第458题可怜的小猪

题目&#xff1a; 题解&#xff1a; func poorPigs(buckets, minutesToDie, minutesToTest int) int {if buckets 1 {return 0}combinations : make([][]int, buckets1)for i : range combinations {combinations[i] make([]int, buckets1)}combinations[0][0] 1iterations…...

欢聚时代(BIGO)Android面试题及参考答案

网络 TCP 和 UDP 协议的区别是什么? TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)是两种不同的传输层协议,它们有以下主要区别: 一、连接性 TCP 是面向连接的协议。在通信之前,需要通过三次握手建立连接,通信结束…...

[C语言]指针和数组

目录 1.数组的地址 2.通过指针访问数组 3.数组和指针的不同点 4.指针数组 1.数组的地址 数组的地址是什么&#xff1f; 看下面一组代码 #include <stdio.h> int main() { int arr[5] {5,4,3,2,1}; printf("&arr[0] %p\n", &arr[0]); printf(&qu…...

Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9

最近折腾小主机&#xff0c;搭建项目环境&#xff0c;记录相关步骤 数据无价&#xff0c;丢失难复 1. Centos Stream 9备份与恢复 1.1 系统备份 root权限用户执行进入根目录&#xff1a; cd /第一种方式备份命令&#xff1a; tar cvpzf backup.tgz / --exclude/proc --exclu…...

Linux的发展历史与环境

目录&#xff1a; 引言Linux的起源早期发展企业级应用移动与嵌入式系统现代计算环境中的Linux结论 引言 Linux&#xff0c;作为开源操作系统的代表&#xff0c;已经深刻影响了全球的计算环境。从其诞生之初到如今成为服务器、嵌入式系统、移动设备等多个领域的核心&#xff0c…...

Jax(Random、Numpy)常用函数

目录 Jax vmap Array reshape Random PRNGKey uniform normal split choice Numpy expand_dims linspace jax.numpy.linalg[pkg] dot matmul arange interp tile reshape Jax jit jax.jit(fun, in_shardingsUnspecifiedValue, out_shardingsUnspecifiedVa…...

python-pptx 中 placeholder 和 shape 有什么区别?

在 python-pptx 库中&#xff0c;placeholder 和 shape 是两个核心概念。虽然它们看起来相似&#xff0c;但在功能和作用上存在显著的区别。为了更好地理解这两个概念&#xff0c;我们可以通过它们的定义、使用场景以及实际代码示例来剖析其差异。 Python-pptx 的官网链接&…...

王者农药更新版

一、启动文件配置 二、GPIO使用 2.1基本步骤 1.配置GPIO&#xff0c;所以RCC开启APB2时钟 2.GPIO初始化&#xff08;结构体&#xff09; 3.给GPIO引脚设置高/低电平&#xff08;WriteBit&#xff09; 2.2Led循环点亮&#xff08;GPIO输出&#xff09; 1.RCC开启APB2时钟。…...

各省份消费差距(城乡差距)数据(2005-2022年)

消费差距&#xff0c;特别是城乡消费差距&#xff0c;是衡量一个国家或地区经济发展均衡性的重要指标。 2005年-2022年各省份消费差距&#xff08;城乡差距&#xff09;数据&#xff08;大数据&#xff09;.zip资源-CSDN文库https://download.csdn.net/download/2401_84585615/…...

[Linux] 进程创建、退出和等待

标题&#xff1a;[Linux] 进程创建、退出和等待 个人主页水墨不写bug &#xff08;图片来源于AI&#xff09; 目录 一、进程创建fork&#xff08;&#xff09; 1&#xff09; fork的返回值&#xff1a; 2&#xff09;写时拷贝 ​编辑3&#xff09;fork常规用法 4&#xff…...

微软推出针对个人的 “AI伴侣” Copilot 会根据用户的行为模式、习惯自动进化

微软推出了为每个人提供的“AI伴侣”Copilot&#xff0c;它不仅能够理解用户的需求&#xff0c;还能根据用户的日常习惯和偏好进行适应和进化。帮助处理各种任务和复杂的日常生活场景。 它能够根据用户的生活背景提供帮助和建议&#xff0c;保护用户的隐私和数据安全。Copilot…...

BiLSTM时间序列预测实战:用Python搞定股票价格预测(附完整代码)

BiLSTM金融时间序列预测&#xff1a;从理论到实战的Python完整指南 金融市场如同汹涌的海浪&#xff0c;价格波动背后隐藏着无数投资者的决策与情绪。对于量化分析师和算法交易者而言&#xff0c;准确预测这些波动意味着巨大的商业价值。传统的时间序列分析方法如ARIMA在面对非…...

ScintillaNET:提升开发效率的专业代码编辑组件深度解析

ScintillaNET&#xff1a;提升开发效率的专业代码编辑组件深度解析 【免费下载链接】ScintillaNET A Windows Forms control, wrapper, and bindings for the Scintilla text editor. 项目地址: https://gitcode.com/gh_mirrors/sc/ScintillaNET 核心价值定位&#xff1…...

从WiFi4到WiFi7:一张表格看懂所有代际的真实网速差距(附选购建议)

从WiFi4到WiFi7&#xff1a;四代协议性能全景对比与智能组网决策指南 当你在电商平台搜索"WiFi6路由器"时&#xff0c;超过200款不同价位的设备会瞬间涌入视野。从299元的入门款到4999元的旗舰机型&#xff0c;商家宣传的"AX3000"、"BE6500"等参…...

Rockchip Android 12编译踩坑记:手把手教你修改BoardConfig.mk生成userdata.img

Rockchip Android 12编译实战&#xff1a;从BoardConfig.mk修改到userdata.img生成的避坑指南 第一次在Rockchip平台上编译Android 12系统时&#xff0c;我遇到了一个令人抓狂的问题——编译过程看似顺利&#xff0c;但生成的固件烧写到设备后&#xff0c;系统始终无法正常启动…...

从BiomixQA到黄帝内经:聊聊2024年那些‘小而美’的垂直医学问答数据集

2024医学垂直问答数据集全景&#xff1a;从BiomixQA到黄帝内经的实战选型指南 当ChatGPT在通用领域大放异彩时&#xff0c;医学AI的战场正悄然转向那些"小而美"的垂直数据集。不同于通用语料的粗放式训练&#xff0c;专业医学问答需要精确到细胞级的语义理解——一个…...

OpenClaw自动化测试实践:GLM-4.7-Flash驱动脚本执行与结果分析

OpenClaw自动化测试实践&#xff1a;GLM-4.7-Flash驱动脚本执行与结果分析 1. 为什么选择OpenClaw做测试自动化&#xff1f; 上个月接手一个新项目时&#xff0c;我遇到了一个典型的技术矛盾&#xff1a;作为独立开发者&#xff0c;既需要保证代码质量&#xff0c;又没精力手…...

TestNet资产管理平台:从安装到实战,全面超越灯塔的解决方案

1. TestNet资产管理平台&#xff1a;为什么你需要它&#xff1f; 如果你是一名网络安全工程师或者渗透测试人员&#xff0c;肯定对资产管理的繁琐深有体会。传统的资产管理工具要么功能单一&#xff0c;要么操作复杂&#xff0c;而TestNet资产管理系统的出现&#xff0c;彻底改…...

ScanTailor Advanced:免费开源扫描文档处理终极指南

ScanTailor Advanced&#xff1a;免费开源扫描文档处理终极指南 【免费下载链接】scantailor-advanced ScanTailor Advanced is the version that merges the features of the ScanTailor Featured and ScanTailor Enhanced versions, brings new ones and fixes. 项目地址: …...

基于hadoop+spark+hive 机器学习物流管理系统 货运路线规划系统 智慧交通 计算机毕业设计 Echarts可视化

1、项目介绍 技术栈&#xff1a; Python语言、Django框架、Echarts可视化、MySQL数据库、HTML、报表、物流信息、多角色登录、物流管理该系统采用python和django两种常见的框架&#xff0c;通过MVT来实现对数据集 成和分析&#xff0c;从而更好地满足各种需求。此外&#xff0c…...

SiameseAOE中文-base惊艳效果:结构化输出JSON兼容下游BI/报表系统直连

SiameseAOE中文-base惊艳效果&#xff1a;结构化输出JSON兼容下游BI/报表系统直连 1. 模型效果惊艳展示 SiameseAOE通用属性观点抽取模型在中文文本处理方面表现出色&#xff0c;能够从非结构化文本中精准提取结构化信息。最令人印象深刻的是&#xff0c;模型输出的JSON格式数…...