AVL树-详细解析【数据结构】

AVL树是首个被发明的自平衡二叉查找树,在1962年由两位苏联科学家G.M. Adelson-Velsky和E.M. Landis提出。AVL树得名于发明者的首字母。在AVL树中,任何节点的两个子树的高度最大差别为一,确保了树的平衡度,使得查找操作相比于普通的二叉查找树更加高效。

AVL树的性质

AVL树保持了二叉查找树的基础性质,即对于任何一个节点,其左子树上的所有节点的值都小于该节点的值,右子树所有节点的值都大于该节点的值。此外,AVL树增加了以下平衡条件:

  • 平衡因子: AVL树中的每个节点的平衡因子被定义为左子树的高度减去右子树的高度(有些定义为相反)。给定的每个节点的平衡因子必须是-1、0或1。

这种强制的平衡条件确保树的最坏情况下的高度为O(log n),其中n是树中节点的数量,从而也保证了查找/插入/删除操作都可以在对数时间内完成。

AVL树的旋转操作

保持AVL树平衡的主要机制是通过旋转操作,分为四种基本旋转:

  1. LL(左左)旋转: 当在节点的左子树的左侧插入导致不平衡时进行。通过一个右旋转来平衡树。

  2. RR(右右)旋转: 当在节点的右子树的右侧插入导致不平衡时进行。通过一个左旋转来平衡树。

  3. LR(左右)旋转: 当在节点的左子树的右侧插入导致不平衡时进行。首先在导致失衡节点的左子树上进行左旋转,然后对该节点进行右旋转。

  4. RL(右左)旋转: 当在节点的右子树的左侧插入导致不平衡时进行。首先在导致失衡节点的右子树上进行右旋转,然后对该节点进行左旋转。

这些旋转能够在维持二叉查找树的性质的同时回复AVL树特有的平衡性质。

AVL树的插入操作

向AVL树插入一个新的节点大体分为三步:

  1. 常规二叉搜索树插入: 首先按照二叉搜索树的规则将节点插入正确位置。

  2. 更新平衡因子: 从插入点开始,向上遍历回根节点,更新祖先节点的平衡因子。

  3. 重新平衡(如果必要): 如果在更新过程中某个节点的平衡因子变为非-1、0或1,将对该节点进行一次或多次旋转操作以恢复平衡。这可能涉及上述四种旋转中的任何一种或复合旋转。

AVL树的删除操作

从AVL树中删除一个节点包括了以下步骤:

  1. 常规二叉搜索树删除: 遵循二叉搜索树的规则,找到并删除该节点,这可能涉及将节点与其直接后继互换的操作。

  2. 更新平衡因子: 从删除节点的父节点开始,向上遍历到根节点,更新节点的平衡因子。

  3. 重新平衡(如果必要): 如果在更新过程中某个节点的平衡因子不是-1、0或1,将对该节点执行相应的旋转操作恢复平衡。

删除操作要复杂些,因为可能需要在不同层上多次进行平衡调整。

深度分析

AVL树的设计主旨是维持二叉查找树在动态更新操作下的平衡,从而避免性能恶化。为了保持深入的讨论,我们将分别撷取关键方面进行详尽的分析。

平衡因子计算

AVL树中每个节点N都有一个平衡因子(BF),它是根据以下公式定义的:

平衡因子(BF) = 高度(左子树) - 高度(右子树)

常规情况下,左子树或右子树的高度的计算是从当前节点向下递归到叶子节点,计算最长路径上边的数量,叶子节点的高度定义为0。因此:

  • BF = 0 表示左子树和右子树的高度相同。
  • BF = -1 表示右子树比左子树高1层。
  • BF = 1 表示左子树比右子树高1层。

AVL树要求所有节点的平衡因子绝对值必须小于或等于1。每次插入或删除操作后,都需要更新从影响节点到根节点路径上所有节点的平衡因子,并进行相应的旋转操作以恢复平衡性。

旋转操作

旋转是用来恢复AVL树平衡的一种操作。在不同的情形下,可能需要不同种类的旋转操作:

LL旋转

当一个节点N的左子树的左边产生了一个新节点,并造成不平衡时,会进行LL旋转。以下是LL旋转的步骤:

  1. 设N为失衡节点,L为N的左子节点。
  2. 将L的右子树移动成N的左子树。
  3. 将N变成L的右子树。
RR旋转

这是LL的镜像操作,当一个节点N的右子树的右边产生了一个新节点,并造成不平衡时,会进行RR旋转。以下是RR旋转的步骤:

  1. 设N为失衡节点,R为N的右子节点。
  2. 将R的左子树移动成N的右子树。
  3. 将N变成R的左子树。
LR旋转

当一个节点N的左子树的右边产生了一个新节点,并造成不平衡时,会进行LR旋转。LR旋转首先进行L的RR旋转,然后对N进行LL旋转。

  1. 设N为失衡节点,L为N的左子节点,LR为L的右子节点。
  2. 对L进行RR旋转。
  3. 对N进行LL旋转。
RL旋转

这是LR旋转的镜像,当一个节点N的右子树的左边产生了一个新节点,并造成不平衡时,会进行RL旋转。

  1. 设N为失衡节点,R为N的右子节点,RL为R的左子节点。
  2. 对R进行LL旋转。
  3. 对N进行RR旋转。

插入操作

插入操作可以概括为以下步骤:

  1. 插入新节点X,像在常规二叉查找树中那样。
  2. 更新从X到根节点的路径上所有节点的平衡因子。
  3. 检查这些节点的平衡因子,如果发现任何节点的平衡因子变为2或-2,那么从最低的不平衡点开始进行旋转操作来恢复平衡。

伪代码示例:

function insert(node, value)if node == nullreturn newNode(value)if value < node.valuenode.left = insert(node.left, value)else if value > node.valuenode.right = insert(node.right, value)elsereturn node// 更新节点的高度node.height = 1 + max(height(node.left), height(node.right))// 获取平衡因子balance = getBalance(node)// 平衡if balance > 1 and value < node.left.valuereturn rightRotate(node)if balance < -1 and value > node.right.valuereturn leftRotate(node)if balance > 1 and value > node.left.valuenode.left = leftRotate(node.left)return rightRotate(node)if balance < -1 and value < node.right.valuenode.right = rightRotate(node.right)return leftRotate(node)return node

删除操作

删除操作遵循以下步骤:

  1. 在树中定位并删除指定节点。
  2. 更新从删除节点的父节点到根节点的路径上所有节点的平衡因子。
  3. 对失去平衡的节点进行旋转操作,恢复平衡。

伪代码示例:

function deleteNode(root, value)// STEP 1: PERFORM STANDARD BST DELETEif root == nullreturn rootif value < root.valueroot.left = deleteNode(root.left, value)else if value > root.valueroot.right = deleteNode(root.right, value)else// node with only one child or no childif (root.left == null) or (root.right == null)temp = nullif temp == root.lefttemp = root.rightelsetemp = root.leftif temp == null // No child casetemp = rootroot = nullelse // One child caseroot = temp // Copy the contents of the non-empty childelse// node with two children: Get the inorder successortemp = minValueNode(root.right)root.value = temp.valueroot.right = deleteNode(root.right, temp.value)if root == nullreturn root// STEP 2: UPDATE HEIGHT OF THE CURRENT NODEroot.height = max(height(root.left), height(root.right)) + 1// STEP 3: GET THE BALANCE FACTOR OF THIS NODEbalance = getBalance(root)// If this node becomes unbalanced, then there are 4 cases// Left Left Caseif balance > 1 and getBalance(root.left) >= 0return rightRotate(root)// Left Right Caseif balance > 1 and getBalance(root.left) < 0root.left = leftRotate(root.left)return rightRotate(root)// Right Right Caseif balance < -1 and getBalance(root.right) <= 0return leftRotate(root)// Right Left Caseif balance < -1 and getBalance(root.right) > 0root.right = rightRotate(root.right)return leftRotate(root)return root

操作效率分析

由于AVL树的严格平衡特性,插入和删除操作可能要求多次旋转,这增加了操作的复杂性。尽管如此,由于树的高度被严格控制在O(log n),因此插入和删除操作的最坏情况时间复杂度为O(log n)。实际的旋转操作仅涉及改变节点几个指针,因此可以认为是O(1)操作,不过这会在插入或删除后沿着路径向上逐步进行,因此总的旋转成本是O(log n)。由于我们只会在最坏的情况下沿着路径上旋转一次,所以这样的成本是可以接受的。

不同旋转操作情景的剖析

旋转操作虽然以四类基本操作来定义,但是每种操作对树的结构和平衡因子都有不同的影响。例如,LL旋转是对树的一边性质最直接的补救,它通过一次简单的旋转即可恢复平衡。而LR旋转则涉及到两个步骤,首先是对失衡节点的左子节点进行RR旋转,然后对失衡节点本身进行LL旋转。这意味着在执行LR旋转时,我们需要更新不仅是失衡节点,同时也要更新其左子节点及LR节点的平衡因子。从逻辑上讲,LR旋转处理的是两种不平衡因子的组合,因此在理解和实现时需要更多的注意。

AVL树的维护需要对树的结构和旋转操作有深刻的理解。每次插入和删除操作后都可能要求维护这种平衡,而这种保持平衡的要求是AVL树能够保持其性能特点的根本。

如果你想更深入地了解人工智能的其他方面,比如机器学习、深度学习、自然语言处理等等,也可以点击这个链接,我按照如下图所示的学习路线为大家整理了100多G的学习资源,基本涵盖了人工智能学习的所有内容,包括了目前人工智能领域最新顶会论文合集和丰富详细的项目实战资料,可以帮助你入门和进阶。

链接: 人工智能交流群【最新顶会与项目实战】(点击跳转)

在这里插入图片描述

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

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

相关文章

JavaScript数组分组groupBy

JavaScript 最近发布了一个方法 Object.groupBy&#xff0c;可以对可迭代对象中的元素进行分组。 语法&#xff1a; Object.groupBy(items, callbackFn)items 被分组的可迭代对象&#xff0c;如 Array。 callbackFn 对可迭代对象中的每个元素执行的函数。 举个例子&#…

【Qt QML 入门】TextEdit

TextEdit可以显示多行可编辑的格式化文。默认是无边框的&#xff0c;可以和父控件完美融合。 import QtQuick import QtQuick.Window import QtQuick.ControlsWindow {id: winwidth: 800height: 600visible: trueTextEdit {id: textEditanchors.centerIn: parenttext: "He…

并发编程中常见的设计模式

文章目录 一、 终止线程的设计模式1. 简介2. Tow-phase Termination&#xff08;两阶段终止模式&#xff09;—优雅的停止线程 二、避免共享的设计模式1. 简介2. Immutability模式—想破坏也破坏不了3. Copy-on-Write模式4. Thread-Specific Storage模式—没有共享就没有伤害 三…

八大排序——快速排序(霍尔 | 挖空 | 前后指针 | 非递归)

我们今天来讲讲八大排序中的快速排序&#xff0c;快速排序最明显的特点就是排序快&#xff0c;时间复杂度是O&#xff08;N* logN&#xff09;&#xff0c;但是坏处就是如果排序的是一个逆序的数组的时候&#xff0c;时间复杂度是O&#xff08;N^2&#xff09;,还不用我们的插入…

被带偏的中国云计算,重归正途

文 | 智能相对论 作者 | 叶远风 阿里云战略聚焦公共云&#xff0c;对整个云计算市场而言都是一场自我审视。 从市场背景、行业发展、中外对比等多个方面&#xff0c;业界舆论给出了大量详实的数据分析&#xff0c;已经对阿里云为什么要聚焦公共云有了结论&#xff0c;这里不…

会 C# 应该怎么学习 C++?

会 C# 应该怎么学习 C&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「C的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&a…

云渲染技术下的虚拟现实:技术探索与革新思考

虚拟现实&#xff08;含增强现实、混合现实&#xff09;是新一代信息技术的重要前沿方向&#xff0c;是数字经济的重大前瞻领域&#xff0c;将深刻改变人类的生产生活方式&#xff0c;产业发展战略窗口期已然形成。但是虚拟现实想要深入改变影响我们的生活&#xff0c;以下技术…

数据结构和算法-最小生成树(prim和krusakal)和最短路径问题(BFS和dijkastra和floyd)

文章目录 最小生成树总览生成树广度优先生成树深度优先生成树最小生成树Prim算法Kruskal算法Prim vs KrusakalPrim的实现Kruskal的实现 小结 最短路径问题单源最短路径问题BFS求无权图的单源最短路径小结Dijkastra算法算法时间复杂度不适用情况 每一对顶点的最短路径问题Floyd算…

SQL Server 远程连接服务器数据库

本文解决sql server的远程连接问题。需要开启防火墙&#xff0c;开启端口&#xff0c;并处理权限不足的报错: 【use 某数据库】The server principal "[server]" is not able to access the database "[database]" under the current security context. 【…

探秘 AJAX:让网页变得更智能的异步技术(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

低代码发展现状调研和思考

低代码开发是近年来迅速崛起的软件开发方法&#xff0c;让编写应用程序变得更快、更简单。有人说它是美味的膳食&#xff0c;让开发过程高效而满足&#xff0c;但也有人质疑它是垃圾食品&#xff0c;缺乏定制性与深度。你认为低代码到底是美味的膳食还是垃圾食品呢&#xff0c;…

C++初学者使用Dev-C++5.11必备的小技巧

一、安装的软件是英文怎么办?陈老师来帮你解决! 步骤1:打开软件,不用我交了吧,看见一个单词长的像 Tools,看见了吧 步骤2:对,找到那个红色框子框起来的单词,最长的那个 步骤3:对,继续选择红色框子里的简体中文/Chinese,不是下面那个,注意,不要选错哟 步骤4:点击…

MySQL | 往数据库中插入时间时,差了八个小时(时区设置)

一&#xff1a;问题 在往数据库中插入&#xff08;读取&#xff09;时间的时候&#xff0c;会相差八个小时&#xff0c;这是常见的问题&#xff0c;原因是因为时区设置的问题 二&#xff1a;知识点 UTC&#xff1a;Coordinated Universal Time 协调世界时。 GMT&#xff1a;…

C++ 报错 error invalid types ‘int[int]‘ for array subscript 原因及解决方案

一般是数组的问题&#xff0c;目前总结出3种可能&#xff1a; 1、数组变量名不一致&#xff0c;或者没定义。比如你定义了一个ans数组&#xff0c;但是你在用的时候误写成了a数组&#xff08;oj应该爆CE&#xff09; 2、数组空间不够&#xff0c;访问越界。比如你要访问a[6]&a…

Unity 控制刚体的移动与旋转的方法

在场景创建一个Cube,并添加刚体&#xff0c;如图&#xff1a; 编写脚本&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;[RequireComponent(typeof(Rigidbody))] public class RibRotate : MonoBehaviour {//private Vector3 mo…

使用LM Studio在本地运行LLM完整教程

GPT-4被普遍认为是最好的生成式AI聊天机器人&#xff0c;但开源模型一直在变得越来越好&#xff0c;并且通过微调在某些特定领域是可以超过GPT4的。在开源类别中出于以下的原因&#xff0c;你可能会考虑过在本地计算机上本地运行LLM &#xff1a; 脱机:不需要互联网连接。模型…

nginx_rtmp_module 之 ngx_rtmp_mp4_module 的mp4源码分析

一&#xff1a;整体代码函数预览 static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf) {ngx_rtmp_play_main_conf_t *pmcf;ngx_rtmp_play_fmt_t **pfmt, *fmt;pmcf ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);pfmt ngx_ar…

AR眼镜_AR智能眼镜整机硬件方案定制

AR眼镜的主要模块包括显示、光学模组、传感器和摄像头、主板、音频和网络连接等。其中&#xff0c;光学显示、主板处理器是决定AR眼镜成本的关键&#xff0c;光机占整体AR眼镜成本43%、处理器占整体成本31%。 AR眼镜的主板设计难点在于尺寸要足够小且要处理好散热问题。主板上的…

接口优先于反射机制

在Java中&#xff0c;使用接口通常比反射机制更为优雅和安全。接口提供了一种声明性的方式来定义类的契约&#xff0c;并且能够在编译时进行类型检查&#xff0c;而反射则是在运行时动态获取和操作类的信息。下面是一个简单的例子&#xff0c;说明为什么在某些情况下接口比反射…