数据结构(AVL树)

BST的退化

仔细观察BST你会发现,虽然他有良好的“搜索”特性,也就是:你可以利用其节点之间的大小关系,很容易地从根节点开始往下走找到你要的节点,但他却无法保证这种搜索所需要的时间的长短,因为建立BST时节点的随机性可能会导致他极其“不平衡”,什么叫不平衡呢?来看一个例子便马上知晓。

假设你创建了一棵空的BST,然后你接连依次插入节点1、2、3、4、5,这会出现什么情况呢?利用draw()函数来帮我们一看究竟,你会发现这棵BST将变成成这样
在这里插入图片描述

平衡树的定义

这棵“畸形”的二叉树虽然依旧是一棵标准的BST,但却已经明显左轻右重了,事实上这棵二叉树已经退化成了一条链表,在这棵BST中搜索某一节点的时间复杂度跟链表是一样的。
这种“左轻右重”或者“左重右轻”的长短腿的情形,就是所谓的不平衡,一棵树如果不平衡,那么他的搜索性能将会受到影响,具体来讲:当树保持平衡时,其搜索时间复杂度是O(log2n)O(log2​n),当树退化成链表时,其搜索时间复杂度变成O(n)O(n),其他情况下树的平均搜索时间复杂度就介于这两者之间。现在的目标,就是要升级我们的BST,使之带有自平衡的特性,当他发现自己的左腿长或者右腿长的时候,会及时调整,保持平衡。

要达到此目的,首先需要量化所谓的“平衡”,平衡的严格数学定义是:在一棵树中,如果其任意一个节点的左、右子树的高度差绝对值小于等于1,那么它就是平衡的。比如下面几棵树都是平衡树:

在这里插入图片描述

上图中每一个节点右上方的数字代表以其为根的树的高度,如果一个节点的子树为空,那么规定空树的高度为0。再来看两棵非平衡树:

在这里插入图片描述

图中红色的节点就是失去平衡的节点,左边二叉树的根节点的右腿太长,右边的是左腿太长,不管是哪种情况,左右两边的子树高度差绝对值都已经超过了1。

AVL树

所谓AVL树,就是要求树中的任何一棵子树的高度差都严格小于或等于1。构造AVL树的关键,是引入一种被称为“旋转”的特殊操作。

不平衡产生的原因

下图中填充了斜线的节点代表新插入的节点,是他们的插入导致了红色节点出现了不平衡。例如第一种情况:原先红节点的左子树已经较其右子树高,现在其左子树又新增了一个左子节点,于是这种不平衡被称为“左-左不平衡”。类似地,如果其左子树新增的是一个右子节点也会导致不平衡,这就是第二种情况“左-右不平衡”。而第三和第四种情况,则完全是对称的。

在这里插入图片描述

跟插入算法一样,删除节点的时候也会导致原来平衡的树变得不再平衡,而不平衡的类型跟插入时导致的四种类型是完全一样的,请看下图:

旋转操作

以第一种“左-左不平衡”为例,应该怎么处理呢?注意这里的“处理”指的是:既要维持原有的二叉搜索树的特性,即“小-中-大”的搜索特性,又要使得它恢复平衡。以前在讨论各种线性表的时候,都会设计一套其常规操作,例如初始化、插入、删除、查询节点、遍历等等,二叉树也不例外,但是二叉树实际上还有一类标准常规操作,他们叫旋转。处理“左-左不平衡”就要用到旋转操作。

在这里插入图片描述

按照上图所示,所谓“右旋转”是一种对树的节点的常规操作,形象上讲,就是将不平衡子树的的根(节点3)按下去,将其原先的左孩子(节点2)提上来,从图上看就好像整棵树被向右(顺时针)旋转了一下,所以叫右旋转。注意:旋转了之后确实重新恢复了平衡,而且各个节点之间也保持了二叉搜索树的“小–中–大”的特性。

AVL树代码设计

树节点

综上可知,对 AVL 树的操作的其中一个核心要素是各个子树的高度,因此,在设计AVL 树节点的时候,通常加入表征以该节点为根的子树的高度是一个比较常见的做法,例如:

typedef struct node
{datatype data; // 节点数据int height;    // 以该节点为根的子树的高度struct node *lchild; // 左子树根指针struct node *rchild; // 右子树根指针
}treenode, *linktree;

旋转操作

以上述逻辑为基调,将旋转操作分成4组函数,其核心参考代码如何:

// 左旋转:
// 将root的左子树压下去,右子树抬起来作为新的根
linktree avlRotateLeft(linktree root)
{linktree tmp = root->rchild;root->rchild = tmp->lchild;tmp->lchild = root;root->height = MAX(height(root->lchild), height(root->rchild)) + 1;tmp->height = MAX(root->height, height(tmp->rchild)) + 1;return tmp;
}// 右旋转:
// 将root的右子树压下去,左子树抬起来作为新的根
linktree avlRotateRight(linktree root)
{linktree tmp = root->lchild;root->lchild = tmp->rchild;tmp->rchild = root;root->height = MAX(height(root->lchild), height(root->rchild)) + 1;tmp->height = MAX(height(tmp->lchild), root->height) + 1;return tmp;
}// 左右旋转:
// 先对左子树执行左旋转,再对根执行右旋转
linktree avlRotateLeftright(linktree root)
{root->lchild = avlRotateLeft(root->lchild);return avlRotateRight(root);
}// 右左旋转:
// 先对右子树执行右旋转,再对根执行左旋转
linktree avlRotateRightleft(linktree root)
{root->rchild = avlRotateRight(root->rchild);return avlRotateLeft(root);
}

插入节点操作

有了节点和旋转函数,就可以在一棵空树的基础上,不断地按照 AVL 的逻辑插入新的节点,注意到:AVL 树本质上也是一棵 BST,因此对它的插入操作必然要先满足 BST 的逻辑,也就是说前面半部分是BST 的插入代码,只不过插入完节点之后,还需考虑平衡性方可退出插入函数。以下是 AVL 树的插入操作的参考代码:

// AVL 树的插入操作
linktree avlInsert(linktree root, linktree new)
{// 若为空树,则直接将 new 作为新的根if(root == NULL)return new;// 按 BST 的逻辑,插入新的节点if(new->data < root->data)root->lchild = avlInsert(root->lchild, new);else if(new->data > root->data)root->rchild = avlInsert(root->rchild, new);else{printf("%d is already exist.\n", new->data);}// 插入完节点之后,判断是否发生4中不平衡中的一种// 如果是,则执行对应的旋转操作if(height(root->lchild) - height(root->rchild) == 2){// 发生了“左左不平衡”: 执行右旋转操作if(new->data < root->lchild->data)root = avlRotateRight(root);// 发生了“左右不平衡”: 执行左右旋转操作else if(new->data > root->lchild->data)root = avlRotateLeftRight(root);}else if(height(root->rchild) - height(root->lchild) == 2){// 发生了“右右不平衡”: 执行左旋转操作if(new->data > root->rchild->data)root = avlRotateLeft(root);// 发生了“右左不平衡”: 执行右左旋转操作else if(new->data < root->rchild->data)root = avlRotateRightLeft(root);}// 更新根节点高度root->height = MAX(height(root->lchild), height(root->rchild)) + 1;return root;
}

删除节点操作

同理,在 AVL 树的节点删除中,首先考虑到 AVL 是一棵 BST,因此完全可以按照 BST 的逻辑先进性一波处理,处理完了再考虑平衡性的问题,参考代码如下:

// AVL 树的删除操作
linktree avlRemove(linktree root, datatype data)
{if(root == NULL)return NULL;// 按 BST 逻辑删除指定节点if(data < root->data)root->lchild = avlRemove(root->lchild, data);else if(data > root->data)root->rchild = avlRemove(root->rchild, data);else{linktree p;if(root->lchild != NULL){for(p=root->lchild; p->rchild!=NULL; p=p->rchild){;}root->data = p->data;root->lchild = avlRemove(root->lchild, p->data);}else if(root->rchild != NULL){for(p=root->rchild; p->lchild!=NULL; p=p->lchild){;}root->data = p->data;root->rchild = avlRemove(root->rchild, p->data);}else{free(root);return NULL;}}// 删除完节点之后,判断是否发生4中不平衡中的一种// 如果是,则执行对应的旋转操作if(height(root->lchild) - height(root->rchild) == 2){// 发生了“左左不平衡”: 执行右旋转操作if(height(root->lchild->lchild)-height(root->lchild->rchild) == 1)root = avlRotateRight(root);// 发生了“左右不平衡”: 执行左右旋转操作elseroot = avlRotateLeftRight(root);}else if(height(root->rchild) - height(root->lchild) == 2){// 发生了“右右不平衡”: 执行左旋转操作if(height(root->rchild->rchild)-height(root->rchild->lchild) == 1)root = avlRotateLeft(root);// 发生了“右左不平衡”: 执行右左旋转操作elseroot = avlRotateRightLeft(root);}root->height = MAX(height(root->lchild), height(root->rchild)) + 1;return root;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAP 01-初识AMDP(ABAP-Managed Database Procedure)

1. 什么是AMDP(ABAP-Managed Database Procedure) 1.&#xff09;AMDP - ABAP管理数据库程序&#xff0c;是一种程序&#xff0c;我们可以使用SQLSCRIPT在AMDP内部编写代码&#xff0c;SQLSCRIPT是一种与SQL脚本相同的数据库语言&#xff0c;这种语言易于理解和编码。 将AM…

Anaconda环境配置(Windows11+python3.9)

文章目录 一、 下载ANACONDA&#xff08;1&#xff09;点击**Free Download**。&#xff08;2&#xff09;点击“skip registration”&#xff0c;跳过登录。&#xff08;3&#xff09;下载对应操作系统的ANACONDA版本。 二、 安装ANACONDA&#xff08;1&#xff09;双击运行安…

Git命令行的使用

目录 一、什么是Git 1、本地仓库 vs 远端仓库 本地仓库 远端仓库 2、.git vs .gitignore .git .gitignore 二、使用Git命令 1、安装git 2、git首次使用需要配置用户邮箱和用户名 3、上传目录/文件到远端仓库步骤 1&#xff09;创建放置文件的目录 2&#xff09;cd…

后台管理系统动态面包屑Breadcrumb组件的实现

在后管理系统开发中&#xff0c;面包屑导航是一个非常常见的功能&#xff0c;通常是根据当前的 url 自动生成面包屑导航菜单&#xff0c;当跳转路由发生变化时&#xff0c;面包屑导航都会随之发生变化&#xff0c;即动态面包屑。 要完成动态面包屑我们需要制作一个动态数组&am…

小程序租赁系统开发的优势与应用前景分析

内容概要 小程序租赁系统是一种新兴的数字化解决方案&#xff0c;旨在为用户提供更加便捷与高效的租赁服务。它通常包括一系列功能&#xff0c;如在线浏览、即时预定、支付功能以及用户反馈机制。这些系统在使用上极为友好&#xff0c;让用户能够轻松选择所需的商品或服务&…

凸包(convex hull)简述

凸包&#xff08;convex hull&#xff09;简述 这里主要介绍二维凸包&#xff0c;二维凸多边形是指所有内角都在 [ 0 , Π ] [0,\Pi ] [0,Π]范围内的简单多边形。 凸包是指在平面上包含所有给定点的最小凸多边形。 数学定义&#xff1a;对于给定集合 X X X&#xff0c;所有…

小波与傅里叶变换在去噪效果上的对比分析-附Matlab源程序

&#x1f468;‍&#x1f393; 博主简介&#xff1a;博士研究生 &#x1f52c; 超级学长&#xff1a;超级学长实验室&#xff08;提供各种程序开发、实验复现与论文指导&#xff09; &#x1f4e7; 个人邮箱&#xff1a;easy_optics126.com &#x1f56e; 目 录 摘要一、…

CVPR2019 | AA | 特征空间扰动产生更具迁移性的对抗样本

Feature Space Perturbations Yield More Transferable Adversarial Examples 摘要-Abstract引言-Introduction相关工作-Related WorkTransferability Metrics-迁移性指标激活攻击方法-Activation Attack Methodology损失函数-Loss Function攻击算法-Attack Algorithm 实验设置…

游戏如何检测Root权限

Root权限&#xff0c;即超级用户权限&#xff0c;在Android系统中&#xff0c;获取Root权限意味着用户可以修改系统文件、移除预装应用、安装特殊应用等。 在Root环境下&#xff0c;游戏面临着相当大的安全隐患&#xff0c;用户获取了最高权限&#xff0c;意味着可以通过各类工…

MySQL性能优化explain关键字详解

系列文章目录 一、MySQL数据结构选择 二、MySQL性能优化explain关键字详解 三、MySQL索引优化 文章目录 系列文章目录一、explain是什么&#xff1f;二、explain字段详解2.1、ID2.2、select_type2.3、table2.4、partitions2.5、type&#xff08;重点&#xff09;2.6、key2.7、…

【Go学习】-01-5-网络编程

【Go学习】-01-5-网络编程 1 互联网协议介绍1.1 互联网分层模型 2 Go网络编程2.1 socket编程2.1.1 socket图解2.2.2 TCP编程2.2.3 UDP编程 2.3 http编程2.3.1 web工作流程2.3.2 HTTP协议 2.4 WebSocket编程2.5 聊天室的小例子2.5.1 server.go文件代码2.5.2 hub.go文件代码2.5.3…

推荐系统重排:MMR 多样性算法

和谐共存&#xff1a;相关性与多样性在MMR中共舞 推荐系统【多样性算法】系列文章&#xff08;置顶&#xff09; 1.推荐系统重排&#xff1a;MMR 多样性算法 2.推荐系统重排&#xff1a;DPP 多样性算法 引言 在信息检索和推荐系统中&#xff0c;提供既与用户查询高度相关的文…

简历_熟悉缓存高并发场景处理方法,如缓存穿透、缓存击穿、缓存雪崩

系列博客目录 文章目录 系列博客目录1.缓存穿透总结 2.缓存雪崩3.缓存击穿代码总结 1.缓存穿透 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库。 常见的解决方案有两种&#xff1a; 缓存空对…

Rabbitmq追问1

如果消费端代码异常&#xff0c;未手动确认&#xff0c;那么这个消息去哪里 2024-12-31 21:19:12 如果消费端代码发生异常&#xff0c;未手动确认&#xff08;ACK&#xff09;的情况下&#xff0c;消息的处理行为取决于消息队列的实现和配置&#xff0c;以下是基于 RabbitMQ …

STM32-笔记37-吸烟室管控系统项目

一、项目需求 1. 使用 mq-2 获取环境烟雾值&#xff0c;并显示在 LCD1602 上&#xff1b; 2. 按键修改阈值&#xff0c;并显示在 LCD1602 上&#xff1b; 3. 烟雾值超过阈值时&#xff0c;蜂鸣器长响&#xff0c;风扇打开&#xff1b;烟雾值小于阈值时&#xff0c;蜂鸣器不响…

2、pycharm常用快捷命令和配置【持续更新中】

1、常用快捷命令 Ctrl / 行注释/取消行注释 Ctrl Alt L 代码格式化 Ctrl Alt I 自动缩进 Tab / Shift Tab 缩进、不缩进当前行 Ctrl N 跳转到类 Ctrl 鼠标点击方法 可以跳转到方法所在的类 2、使用pip命令安装request库 命令&#xff1a;pip install requests 安装好了…

SpringCloud系列教程:微服务的未来(八)项目部署、DockerCompose

本博客将重点介绍如何在 Docker 环境中部署一个 Java 项目&#xff0c;并使用 Docker Compose 来简化和管理多个服务的协调部署。我们将通过一个典型的 Java Web 应用&#xff08;如基于 Spring Boot 的应用&#xff09;为例&#xff0c;演示如何构建、配置和运行 Docker 容器&…

微信小程序滑动解锁、滑动验证

微信小程序简单滑动解锁 效果 通过 movable-view &#xff08;可移动的视图容器&#xff0c;在页面中可以拖拽滑动&#xff09;实现的简单微信小程序滑动验证 movable-view 官方说明&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.ht…

Conda 安装 Jupyter Notebook

文章目录 1. 安装 Conda下载与安装步骤&#xff1a; 2. 创建虚拟环境3. 安装 Jupyter Notebook4. 启动 Jupyter Notebook5. 安装扩展功能&#xff08;可选&#xff09;6. 更新与维护7. 总结 Jupyter Notebook 是一款非常流行的交互式开发工具&#xff0c;尤其适合数据科学、机器…

【小程序开发】- 小程序版本迭代指南(版本发布教程)

一&#xff0c;版本号 版本号是小程序版本的标识&#xff0c;通常由一系列数字组成&#xff0c;如 1.0.0、1.1.0 等。版本号的格式通常是 主版本号.次版本号.修订号 主版本号&#xff1a;当小程序有重大更新或不兼容的更改时&#xff0c;主版本号会增加。 次版本号&#xff1a…