AVL树的创建与检测

个人主页:敲上瘾-CSDN博客

个人专栏:游戏、数据结构、c语言基础、c++学习、算法

目录

一、什么是AVL树?

二、平衡因子

1、什么是平衡因子?

2、平衡因子如何更新?

三、单旋

1、左单旋

​编辑

2、右单旋

四、双旋

1、左右双旋

2、右左双旋

五、AVL树检测

六、源码


一、什么是AVL树?

        什么是AVL树?其实它就是一颗平衡的二叉搜索树,我们都知道一颗二叉搜索树在极端情况下会退化为单支(和链表同样的结构),那么它的查找效率就会变为O(N),AVL树的就是通过一些操作来防止这种退化,从而使查找效率保持在O(logN)。

如下同一组数据的两种二叉搜索树形态:

二、平衡因子

1、什么是平衡因子?

        对一个AVL树操作的时候首先就是需要知道它是否平衡,所以可以在节点上多增加一个变量用来储存左右子树的高度差,这个数据我们就称之为平衡因子。(即平衡因子=左子树的高度-右子树的高度)平衡因子只是起到一个辅助的作用,也可用其他方式。

        现在我们知道了高度差,那么高度差到达什么时候表示不平衡需要我们去调整呢?我们可以知道高度差为零的时候肯定是最好的,但⽽是有些情况是做不到⾼度差为0的。⽐如⼀棵树是2个结点,4个结点等情况下,⾼度差最好就是1。

        所以我们就以平衡因子大于等于2或平衡因子小于等于-2为标准表示该树已经不平衡需要调整。

2、平衡因子如何更新?

        对于一个AVL树(已平衡)我们插入一个数据实际上是往叶子节点插入,而无论如何插入都会影响它的父节点的平衡因子,父节点又会影响它的父节点,以此类推。这样在更新父节点的平衡因子时就会变得很麻烦。所以在设计节点的时候除了有指向左子树的指针和指向右子树的指针以外,还需要我们添加一个前驱指针指向父节点

        平衡因子具体如何更新?因为平衡因子=左子树高度-右子树高度。所以如果新元素插入在左子树则父节点的平衡因子加1,如果插入到右子树则父节点的平衡因子减1。然后继续更新该父节点(记为p)的父节点(记为pp),同样如果p是pp的左子树则pp的平衡因子加1,如果p是pp的右子树则平衡因子减1。以此类推往上更新。

平衡因子更新后一共有三种情况:

  • 平衡因子更新为0:此时表示之前不平衡那一部分因为新节点的插入而被抵消,对整棵树的高度没有影响,所以就不会对它上一层的平衡因子造成影响,不用往上更新。
  • 平衡因子更新为1或-1:此时表示新节点的插入使得整棵树的高度改变,会影响上一层,需要继续更新。
  • 平衡因子更新为2或-2:此时表示新节点的插入使得整棵树的高度改变,并使这棵子树不平衡,需要通过旋转把它调整为平衡状态,而调整后整棵子树高度又回到原来状态,不会对上一层造成影响。不用往上更新。
	bool Insert(const T& val)//插入元素{Node* cur = root;Node* newNode = new Node(val);//如果为空树则直接插入if (root == nullptr){root = newNode;return true;}//查找插入位置Node* parent = nullptr;while (cur){parent = cur;if (val <= cur->data) cur = cur->left;else cur = cur->right;}//插入操作if (val <= parent->data) parent->left = newNode;else parent->right = newNode;newNode->prev = parent;//更新平衡因子cur = newNode;while (parent != nullptr){if (parent->left == cur) parent->bf--;else parent->bf++;if (parent->bf == 0) break;else if (parent->bf == 1 || parent->bf == -1){cur = parent;parent = parent->prev;}else if (parent->bf == 2 || parent->bf == -2){//不平衡,进行旋转//......}}return true;}

三、单旋

1、左单旋

        在这里为了兼容子树的多种状况和不失一般性,使用了a,b,c来抽象表示子树。如上图因为添加节点使得a的高度发生变化(但a子树依旧保持平衡),从而使得存放15这个节点(记为subR)的这棵个子树的高度改变(subR依旧平衡),从而影响上一层(记为parent),parent的平衡因子变为2,即左边过高,使得整棵树不平衡需要调整。

        因为这里有这样一个规律,整颗b子树是比parent大的,所以可以把b子树接到parent右边,然后因为subR同样也比parent大但是要把高的子树提上去,所以把parent接到subR左边。

前驱指针更新

对于单旋的重难点还是在于前驱指针的更新,我们把它们分开单个分析:

  • parent:parent的前驱直接指向subR即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
  • subR:首先将subR的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subR。(2)、pparent不为空,那么pparent同时也要指向subR,此时pparent的左子树还是右子树指向subR是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subR,同理如果是右孩子则pparent的右指向subR。
  • subRL:对于subRL有两种情况:(1)、subRL为空,此时不用处理,(2)、subRL不为空,此时直接把subRL的前驱指针指向parent。

平衡因子更新 

        通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋(包括右单旋)调整后的parent和subR平衡因子都需要更新为0,而在该过程中subRL的平衡因子并未受到影响,不用更新。

左单旋代码:

void RotateL(Node* parent)
{Node* subR = parent->right;Node* subRL = subR->left;Node* pparent = parent->prev;//修改子节点parent->right = subRL;subR->left = parent;//修改父节点parent->prev = subR;if (subRL) subRL->prev = parent;subR->prev = pparent;if (pparent == nullptr) root = subR;//把根节点替换成subLelse{if (pparent->left == parent) pparent->left = subR;else pparent->right = subR;}//修改平衡因子parent->bf = 0;subR->bf = 0;
}

2、右单旋

        如果理解了左单旋那么右单旋就比较容易理解了,同样如果添加节点使得parent左边过高,则需要右旋。如图subL和subLR都比parent要小,但为了把左子树提上去,所以让parent接到subL的右边,subLR接到parent的左边。

前驱指针更新

  • parent:parent的前驱直接指向subL即可,但是注意在这之前一定要先把原来parent的前驱记录起来(记为pparent)
  • subL:首先将subL的前驱指向pparent,而pparent又分为两种情况:(1)、pparent为空,说明原来的parent是整棵树的根,所以现在需要把根更新为subL。(2)、pparent不为空,那么pparent同时也要指向subL,此时pparent的左子树还是右子树指向subL是个问题,我们可以判断parent是pparent的左孩子还是右孩子,如果是左孩子则pparent的左指向subL,同理如果是右孩子则pparent的右指向subL。
  • subLR:对于subLR有两种情况:(1)、subLR为空,此时不用处理,(2)、subLR不为空,此时直接把subLR的前驱指针指向parent。

平衡因子更新 

        通过观察调整后的树,调整后涉及的节点它的左右子树高度都是一样的,所以对于单旋调整后的parent和subL平衡因子都需要更新为0,而在该过程中subLR的平衡因子并未受到影响,不用更新。

右单旋代码:

void RotateR(Node* parent)
{Node* subL = parent->left;Node* subLR = subL->right;Node* pparent = parent->prev;parent->left = subLR;subL->right = parent;//修改前驱parent->prev = subL;if (subLR) subLR->prev = parent;subL->prev = pparent;if (pparent == nullptr) root = subL;else{if (pparent->left == parent) pparent->left = subL;else pparent->right = subL;}parent->bf = 0;subL->bf = 0;
}

四、双旋

1、左右双旋

        对于该场景我们可以发现无论让它左旋还是右旋都无法到达平衡状态,它与单旋的区别是parent和subL的平衡因子是一正一负,也就是说它并不是单纯的左边高或单纯的右边高,如上图对于subL来说是左边高,对于parent来说是右边高。

        不过也好办,只需要先以subL为旋转点进行左旋,再以parent为旋转点进行右旋,这个可以直接复用上面单旋的代码。如下:

void RotateLR(Node* prev)
{RotateL(prev->left);RotateR(prev);
}

不过有一个细节,平衡因子如何改变?我们就需要对b子树展开进行分析,如下:

左右双旋:

以场景一为例动画展示:第一部以subL为旋转点进行左旋,第二部以parent为旋转点进行右旋。

 

        通过观察很容易发现,如果原先subLR平衡因子为-1,则旋转后parent、subL、subLR平衡因子分别为1,0,0。如果原先subLR平衡因子为1,则旋转后parent、subL、subLR平衡因子分别为0,-1,0。对于这个特点我们直接在旋前进行判断直接修改就行。

特殊场景:

对于该场景各节点的平衡因子反而是不需要特殊处理的,单旋过程已经处理过了。

2、右左双旋

        右左双旋相对于左右双旋就是照葫芦画瓢,这里就不做过多的讲解。

旋转代码:

void RotateRL(Node* prev)
{RotateR(prev->right);RotateL(prev);
}

 

        同样如果原先subRL平衡因子为-1,则旋转后parent、subR、subRL平衡因子分别为0,1,0。如果原先subLR平衡因子为1,则旋转后parent、subR、subRL平衡因子分别为-1,0,0。对于这个点我们直接在旋前进行判断直接修改就行。

该场景的平衡因子处理和单旋处理相同,不用做特殊处理。

五、AVL树检测

        当我们写好一个AVL树后然后判断它是否平衡是个问题。注意这里我们不能直接使用平衡因子去判断,平衡因子只是我们用来创建AVL树的一个工具,要看一颗AVL树是否真正平衡则需要去看它的高度差,如果任意一颗子树的高度差大于等于2那么这个树就不平衡。

代码如下:

int Height(Node* root)//计算高度
{if (root == nullptr) return 0;int l = Height(root->left);int r = Height(root->right);return l > r ? l : r;
}
bool _IsAVLTree(Node* root)//判断是否平衡
{if (root == nullptr) return true;int LHeight = Height(root->left);int RHeight = Height(root->right);if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;return _IsAVLTree(root->left) && _IsAVLTree(root->right);
}

六、源码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
template<typename T>
struct AVLTreeNode
{AVLTreeNode(const T& val = T()):left(nullptr), right(nullptr), prev(nullptr), data(val), bf(0) {}AVLTreeNode<T>* left;AVLTreeNode<T>* right;AVLTreeNode<T>* prev;T data;int bf;
};
template<typename T>
class AVLTree
{typedef AVLTreeNode<T> Node;
public:bool Insert(const T& val){Node* cur = root;Node* newNode = new Node(val);if (root == nullptr){root = newNode;return true;}Node* parent = nullptr;while (cur){parent = cur;if (val <= cur->data) cur = cur->left;else cur = cur->right;}if (val <= parent->data) parent->left = newNode;else parent->right = newNode;newNode->prev = parent;cur = newNode;while (parent != nullptr){if (parent->left == cur) parent->bf--;else parent->bf++;if (parent->bf == 0) break;else if (parent->bf == 1 || parent->bf == -1){cur = parent;parent = parent->prev;}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){if (cur->right->bf == -1) parent->bf = 1;else cur->bf = -1;RotateLR(parent);}else if (parent->bf == 2 && cur->bf == -1){if (cur->left->bf == -1) cur->bf = 1;else parent->bf = -1;RotateRL(parent);}else assert(false);return true;}else{assert(0);}}return false;}void RotateL(Node* parent){Node* subR = parent->right;Node* subRL = subR->left;Node* pparent = parent->prev;//修改子节点parent->right = subRL;subR->left = parent;//修改父节点parent->prev = subR;if (subRL) subRL->prev = parent;subR->prev = pparent;if (pparent == nullptr) root = subR;//把根节点替换成subLelse{if (pparent->left == parent) pparent->left = subR;else pparent->right = subR;}//修改平衡因子parent->bf = 0;subR->bf = 0;}void RotateR(Node* parent){Node* subL = parent->left;Node* subLR = subL->right;Node* pparent = parent->prev;parent->left = subLR;subL->right = parent;//修改前驱parent->prev = subL;if (subLR) subLR->prev = parent;subL->prev = pparent;if (pparent == nullptr) root = subL;else{if (pparent->left == parent) pparent->left = subL;else pparent->right = subL;}parent->bf = 0;subL->bf = 0;}void RotateLR(Node* parent){RotateL(parent->left);RotateR(parent);}void RotateRL(Node* parent){RotateR(parent->right);RotateL(parent);}int Height(Node* root){if (root == nullptr) return 0;int l = Height(root->left);int r = Height(root->right);return l > r ? l : r;}bool IsAVLTree(){return _IsAVLTree(root);}bool _IsAVLTree(Node* root){if (root == nullptr) return true;int LHeight = Height(root->left);int RHeight = Height(root->right);if (LHeight - RHeight >= 2 || LHeight - RHeight <= -2) return false;return _IsAVLTree(root->left) && _IsAVLTree(root->right);}
private:Node* root = nullptr;
};

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

c++结构体嵌套

没有很听懂这个课 有点乱了、 // // Created by 徐昌真 on 2024/10/5. // #include <iostream> using namespace std; int main() {struct Point{ //定义一个叫做point的结构体double x, y;};struct Radius{Point pt; //嵌套point结构体在radius结构体里面 把他名字定…

从介质失效看互联网时代的信息过载

来读一篇文章&#xff1a;90年代的硬盘已大规模变砖&#xff0c;没啥好担心的&#xff0c;好事。 结合我两年前的粗浅认知 互联网时代无信息&#xff0c;按照 “动” 的观念看&#xff0c;当信息越来越多&#xff0c;信息密度越来越大时&#xff0c;信息的寿命就会越来越短&am…

k8s实战-1

k8s实战-1 一、资源创建方式1.命令行2.yaml 二、命名空间三、Pod总结 一、资源创建方式 1.命令行 就是直接通过命令的方式创建&#xff0c;比如我要创建namespace&#xff0c; kubectl create namespace hello删除&#xff1a; kubectl delete -f hello2.yaml 简单来说&am…

分布式事务(Seata-AT模式)

角色说明 TC (Transaction Coordinator) - 事务协调者 维护全局和分支事务的状态,驱动全局事务提交或回滚。 TM (Transaction Manager) - 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。 RM (Resource Manager) - 资源管理器 管理分…

发现一篇瑞芯微RK3588上使用Gstreamer的文章(野火)

1. 前言 最近经常使用英伟达的Orin和瑞芯微RK3588做开发,自己还买了好几块开发板,很多需要自己琢磨,今天忽然发现了一篇文章,意外解决了一些之前的问题,以此作为记录: 文章链接:https://doc.embedfire.com/linux/rk356x/quick_start/zh/latest/lubancat_rk_software_har…

【STM32开发之寄存器版】(三)-详解NVIC中断

一、前言 STM32F103ZET6具备强大的中断控制能力&#xff0c;其嵌套向量中断控制器(NVIC)和处理器核的接口紧密相连&#xff0c;可以实现低延迟的中断处理和高效地处理晚到的中断。NVIC主要具备以下特性&#xff1a; 68个可屏蔽中断通道(不包含16个Cortex™-M3的中断线)&#xf…

使用ValueConverters扩展实现枚举控制页面的显示

1、ValueConverters 本库包含了IValueConverter接口的的最常用的实现&#xff0c;ValueConverters用于从视图到视图模型的值得转换&#xff0c;某些情况下&#xff0c;可用进行反向转换。里面有一些抽象类、模板类的定义&#xff0c;可以继承这些类实现一些自己想要实现的功能…

GO网络编程(三):海量用户通信系统1:登录功能

一、准备工作 需求分析 1)用户注册 2)用户登录 3)显示在线用户列表 4)群聊(广播) 5)点对点聊天 6)离线留言 主界面 首先&#xff0c;在项目根目录下初始化mod&#xff0c;然后按照如下结构设计目录&#xff1a; 海量用户通信系统/ ├── go.mod ├── client/ │ ├──…

YOLO11改进|卷积篇|引入全维动态卷积ODConv

目录 一、【ODConv】全维动态卷积1.1【ODConv】卷积介绍1.2【ODConv】核心代码 二、添加【ODConv】卷积2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【ODConv】全维动态卷积 1.1【ODConv】卷积介绍 ODConv利用一种全新的多维注意力机…

操作系统实验之银行算法

一、实验目的 采用高级语言编写一个动态分配系统资源的程序&#xff0c;模拟死锁现象&#xff0c;观察死锁发生的条件&#xff0c;并采用适当的算法&#xff0c;有效地防止死锁的发生。 二、实验内容 本次实验采用银行算法防止死锁的发生。设有3个并发进程共享10个系统资源。在…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…

AQS机制详解

案例一 public class AqsThread extends Thread {private Lock lock;public AqsThread(String name, Lock lock) {super(name);this.lock lock;}Overridepublic void run() {lock.lock();try {System.out.println(Thread.currentThread().getName() "running");} …

【LeetCode】每日一题 2024_10_5 完成旅途的最少时间(二分答案)

前言 每天和你一起刷 LeetCode 每日一题~ 大家国庆节快乐呀~ LeetCode 启动&#xff01; 突然发现&#xff0c;国庆的每日一题&#xff0c;不是坐公交就是坐火车&#xff0c;不是坐火车就是做飞机&#xff0c;这就是你的国庆旅游计划吗&#xff01;力扣&#xff01; 题目&a…

图表不会做怎么办?AI一键生成好看图表!

本期教你如何用AI一键生成各种数据图表&#xff01; 本文阅读难度&#xff1a;★☆☆☆☆ 看看别人做的这些图表&#xff0c;是不是挺好看的&#xff1f; 特别是作为接商单的新写手&#xff0c;看到这些&#xff0c;头都大了&#xff0c;该怎么办呢&#xff1f; 不用怕&…

ModuleNotFoundError: No module named ‘package‘

报错&#xff1a; Traceback (most recent call last): File “”, line 198, in run_module_as_main File “”, line 88, in run_code File "D:\python\helloworld.venv\Scripts\pip.exe_main.py", line 4, in File "D:\python\helloworld.venv\Lib\site-pac…

MAC备忘录空白解决方案

打开icloud->备忘录 取消勾选同步此MAC后再次勾选&#xff0c;然后点击完成即可。

S7-200 SMART的数据类型说明

S7-200 SMART的数据主要分为&#xff1a; 与实际输入/输出信号相关的输入/输出映象区&#xff1a; I&#xff1a;数字量输入&#xff08;DI&#xff09;Q&#xff1a;数字量输出&#xff08;DO&#xff09;AI&#xff1a;模拟量输入AQ&#xff1a;模拟量输出 内部数据存储区…

NVIDIA网卡系列之ConnectX-4规格信息(50G-PCIe 3.0x8-8PF256VF-2015年发布)

背景 NVIDIA ConnectX-4系列的网卡&#xff0c;早期还在Mellanox未被NVIDIA收购的时候就发布了&#xff0c;支持50G&#xff0c;PCIe3.0&#xff0c;最大x8通道lanes。 是50G级别的一代&#xff08;10G-CX3&#xff0c;50G-CX4&#xff0c;100G-CX5&#xff0c;200G-CX6&#…

基于Python的自然语言处理系列(24):BiDAF(双向注意力流)

在自然语言处理领域,机器阅读理解(Machine Comprehension, MC)是一个重要的任务。在这篇博文中,我们将实现论文 BiDAF 中提出的双向注意力流模型。BiDAF 主要改进了传统注意力机制中的早期信息摘要问题,并引入了字符嵌入来加强对单词细粒度信息的理解。 1. 加载 SQuAD 数据…

ThreadLocal底层原理及数据结构详解

ThreadLocal允许为每个线程创建独立的变量副本&#xff0c;使得同一个ThreadLocal对象在不同的线程中拥有不同的值。它的主要作用是在并发环境下提供线程隔离&#xff0c;避免多个线程共享同一个变量&#xff0c;从而减少线程间的相互干扰。 ThreadLocal的核心在于为每个线程维…