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

news2024/11/23 15:36:59

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 == null
        return newNode(value)
    if value < node.value
        node.left = insert(node.left, value)
    else if value > node.value
        node.right = insert(node.right, value)
    else
        return node
    // 更新节点的高度
    node.height = 1 + max(height(node.left), height(node.right))
    // 获取平衡因子
    balance = getBalance(node)
    // 平衡
    if balance > 1 and value < node.left.value
        return rightRotate(node)
    if balance < -1 and value > node.right.value
        return leftRotate(node)
    if balance > 1 and value > node.left.value
        node.left = leftRotate(node.left)
        return rightRotate(node)
    if balance < -1 and value < node.right.value
        node.right = rightRotate(node.right)
        return leftRotate(node)
    return node

删除操作

删除操作遵循以下步骤:

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

伪代码示例:

function deleteNode(root, value)
    // STEP 1: PERFORM STANDARD BST DELETE
    if root == null
        return root
    if value < root.value
        root.left = deleteNode(root.left, value)
    else if value > root.value
        root.right = deleteNode(root.right, value)
    else
        // node with only one child or no child
        if (root.left == null) or (root.right == null)
            temp = null
            if temp == root.left
                temp = root.right
            else
                temp = root.left
            if temp == null // No child case
                temp = root
                root = null
            else // One child case
                root = temp // Copy the contents of the non-empty child
        else
            // node with two children: Get the inorder successor
            temp = minValueNode(root.right)
            root.value = temp.value
            root.right = deleteNode(root.right, temp.value)
    if root == null
        return root
    // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE
    root.height = max(height(root.left), height(root.right)) + 1
    // STEP 3: GET THE BALANCE FACTOR OF THIS NODE
    balance = getBalance(root)
    // If this node becomes unbalanced, then there are 4 cases
    // Left Left Case
    if balance > 1 and getBalance(root.left) >= 0
        return rightRotate(root)
    // Left Right Case
    if balance > 1 and getBalance(root.left) < 0
        root.left = leftRotate(root.left)
        return rightRotate(root)
    // Right Right Case
    if balance < -1 and getBalance(root.right) <= 0
        return leftRotate(root)
    // Right Left Case
    if balance < -1 and getBalance(root.right) > 0
        root.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.coloradmin.cn/o/1317583.html

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

相关文章

JavaScript数组分组groupBy

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

圣诞树绘制合集-python绘制

使用Python绘制迷人的圣诞树 引言 随着圣诞节的临近&#xff0c;我们都希望以各种方式庆祝这个欢乐的节日。作为一名编程爱好者&#xff0c;你有没有想过用Python来创造节日的气氛呢&#xff1f;在这篇文章中&#xff0c;我将向你展示如何用Python绘制几种不同风格的圣诞树&a…

Linux之进程(四)(进程地址空间)

目录 一、程序地址空间 二、进程地址空间 1、概念 2、写时拷贝 3、为什么要有进程地址空间 四、总结 一、程序地址空间 我们先来看看下面这张图。这张图是我们在学习语言时就见到过的内存区域划分图。 下面我们在Linux下看一看内存区域是不是也是这么划分的。 可见在Li…

浅谈MapReduce

MapReduce是一个抽象的分布式计算模型&#xff0c;主要对键值对进行运算处理。用户需要提供两个自定义函数&#xff1a; map&#xff1a;用于接受输入&#xff0c;并生成中间键值对。reduce&#xff1a;接受map输出的中间键值对集合&#xff0c;进行sorting后进行合并和数据规…

RabbitMq交换机详解

目录 1.交换机类型2.Fanout交换机2.1.声明队列和交换机2.2.消息发送2.3.消息接收2.4.总结 3.Direct交换机3.1.声明队列和交换机3.2.消息接收3.3.消息发送3.4.总结 4.Topic交换机4.1.说明4.2.消息发送4.3.消息接收4.4.总结 5.Headers交换机5.1.说明5.2.消息发送5.3.消息接收5.4.…

Linux(23):Linux 核心编译与管理

编译前的任务&#xff1a;认识核心与取得核心原始码 Linux 其实指的是核心。这个【核心(kernel)】是整个操作系统的最底层&#xff0c;他负责了整个硬件的驱动&#xff0c;以及提供各种系统所需的核心功能&#xff0c;包括防火墙机制、是否支持 LVM 或 Quota 等文件系统等等&a…

数据分析为何要学统计学(2)——如何估计总体概率分布

明确总体的概率分布类型及参数是进行数据分析的基础&#xff0c;这项工作称为分布推断与参数估计。在总体分布及其参数不明确的情况下&#xff0c;我们可以利用手头掌握的样本来完成这项工作。具体过程由以下步骤组成。 第一步&#xff0c;样本统计特性直观估计 我们采用Seab…

虚拟机VirtualBox和VMware安装Ubuntu16配置静态IP

计算机集群安装之前&#xff0c;准备先在虚拟机上尝试一下&#xff0c;网上多是采用VMware虚拟机和CentOS系统&#xff0c;个人则准备采用已经安装好的VirtualBox虚拟机和Ubuntu16&#xff0c;但遇到第一个问题即是配置静态IP&#xff0c;那么对于以上两种虚拟机静态IP配置的问…

(数据结构)单链表的查找和长度计算

代码实现 #include<stdio.h> #include<stdlib.h> typedef struct LNode {int data;struct LNode* next; }LNode,*LinkList; //创建头结点 LNode* InitList(LinkList L) {L (LNode*)malloc(sizeof(LNode));if (L NULL){return NULL;}L->data 0;L->next N…

掌握STL中stack和queue的用法(零基础/小白,全方面了解)

目录 1. stack的概念 2. stack的接口 2.1 构造函数&#xff08;初始化&#xff09; 2.2 赋值 2.3 存取操作 2.4 大小操作 3. queue的概念 4. queue的接口 4.1 构造函数 4.2 赋值操作 4.3 存取操作 4.4 大小操作 stack和queue接口函数很少&#xff0c;只要大家多敲一两…

设计模式(3)--对象结构(3)--组合

1. 意图 将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。 2. 三种角色 抽象组件(Component)、组合式节点(Composite)、叶节点(Leaf) 3. 优点 3.1 定义了包含基本对象和组合对象的类层次结构。 客户代码中&…

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

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

【Hive_03】单行函数、聚合函数、窗口函数、自定义函数、炸裂函数

1、函数简介2、单行函数2.1 算术运算函数2.2 数值函数2.3 字符串函数&#xff08;1&#xff09;substring 截取字符串&#xff08;2&#xff09;replace 替换&#xff08;3&#xff09;regexp_replace 正则替换&#xff08;4&#xff09;regexp 正则匹配&#xff08;5&#xff…

操作系统期末复习-内存管理

一、内存管理 分页存储管理&#xff0c;是将一个进程的逻辑地址空间分成若干个大小相等的片&#xff0c;称为页面或页&#xff0c;并为各页加以编号&#xff0c;从0开始&#xff0c;如第0页、第1页等。相应地&#xff0c;也把内存空间分成与页面相同大小的若干个存储块&#xf…

直线追踪

由于项目的需要&#xff0c;最近在做一个直线追踪的东西&#xff0c;但是网上的代码关于车道线或者别的什么之类的直线追踪的代码只是提了一下&#xff0c;相关的代码并不是公开的&#xff0c;所以自己写了一些直线追踪的代码。 代码使用的是kalman滤波进行直线追踪&#xff0…

完美解决labelimg xml转可视化中文乱码问题,不用matplotlib

背景简述 我们有一批标注项目要转可视化&#xff0c;因为之前没有做过&#xff0c;然后网上随意找了一段代码测试完美&#xff08;并没有&#xff09;搞定&#xff0c;开始疯狂标注&#xff0c;当真正要转的时候傻眼了&#xff0c;因为测试的时候用的是英文标签&#xff0c;实…

Sci. Rep. | 一个对任意分子体系实现准确且高效几何深度学习的通用框架

这篇工作是来自纽约城市大学/康奈尔医学院谢磊团队的一篇论文。作者提出了一个通用框架&#xff0c;PAMNet&#xff0c;可以对任意分子体系实现准确且高效的几何深度学习。在小分子性质、RNA三维结构以及蛋白质-配体结合亲和力的预测任务上&#xff0c;PAMNet在准确性和效率方面…

网络编程-认识套接字socket

文章目录 套接字概念端口号网络字节序 套接字类型流套接字数据报套接字 socket常见APIsocket函数bind函数listen函数accept函数connect函数sockaddr结构 套接字概念 socket套接字是进程之间一种通信机制&#xff0c;通过套接字可以在不同进程之间进行数据交流。在TCP/UDP中&…

将html的radio单选框自定义样式为正方形和对号

将html的radio单选框自定义样式为正方形和对号 背景&#xff1a; 如何能把html的<input type"radio" name"option">改成自定义的样式呢&#xff1f;比如想要把他变成正方形&#xff0c;选中的时候是对号。默认的样式太丑了 默认样式&#xff1a; 自…