平衡二叉树(AVL),“平衡”是指什么?为什么要“平衡”?

一、“平衡因子”是什么?

定义:某节点的左子树 右子树高度(深度)差,即为该节点的平衡因子(BF,Balance Factor)。

二、

原文链接:https://blog.csdn.net/kexuanxiu1163/article/details/103080901

前言
Wiki:在 计算机科学中, AVL树是最早被发明的 自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为 高度平衡树。查找、插入和删除在平均和最坏情况下的 时间复杂度都是{\displaystyle O(\log {n})}。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

1、 为什么要有“平衡”二叉树?
二叉搜索树一定程度上可以提高搜索效率,但是当原序列有序时,例如序列 A = {1,2,3,4,5,6},构造二叉搜索树如图 1.1。依据此序列构造的二叉搜索树为右斜树,同时二叉树退化成单链表,搜索效率降低为 O(n)。


在此二叉搜索树中查找元素 6 需要查找 6 次。

二叉搜索树的查找效率取决于树的高度,因此保持树的高度最小,即可保证树的查找效率。(重要!!!)

同样的序列 A,将其改为图 1.2 的方式存储,查找元素 6 时只需比较 3 次,查找效率提升一倍。


可以看出当节点数目一定,保持树的左右“两端保持平衡”,树的查找效率最高。(意义所在,重要!!!)

这种左右子树的高度相差不超过 1 的树为:“平衡”二叉树。

2、定义
平衡二叉查找树:简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质:

1.可以是空树。
2.假如不是空树,任何一个结点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过 1。
平衡之意,如天平,即两边的分量大约相同。

例如图 2.1 不是平衡二叉树,因为结点 60 的左子树不是平衡二叉树。


图 2.2 也不是平衡二叉树,因为虽然任何一个结点的左子树与右子树都是平衡二叉树,但高度之差已经超过 1 。


图 2.3 不是平衡二叉树。


3. 平衡因子
定义:某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor),平衡二叉树中不存在平衡因子大于 1 的节点。在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高。


4. 节点结构
定义平衡二叉树的节点结构:


typedef struct AVLNode *Tree;
 
typedef int ElementType;
 
struct AVLNode{
 
    int depth; //深度,这里计算每个结点的深度,通过深度的比较可得出是否平衡
 
    Tree parent; //该结点的父节点
 
    ElementType val; //结点值
 
    Tree lchild;
 
    Tree rchild;
 
    AVLNode(int val=0) {
        parent = NULL;
        depth = 0;
        lchild = rchild = NULL;
        this->val=val;
    }
};


5. AVL树插入时的失衡与调整
图 5.1 是一颗平衡二叉树


在此平衡二叉树插入节点 99 ,树结构变为:


在动图 5.2 中,节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2,树失去平衡。

在动图 5.2 中,以节点 66 为父节点的那颗树就称为 最小失衡子树。

最小失衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过 1 的结点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。

平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的。根据旋转的方向有两种处理方式,左旋 与 右旋 。

旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转。

5.1 左旋

以图 5.1.1 为例,加入新节点 99 后, 节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2。为保证树的平衡,此时需要对节点 66 做出旋转,因为右子树高度高于左子树,对节点进行左旋操作,流程如下:

(1)节点的右孩子替代此节点位置 (2)右孩子的左子树变为该节点的右子树 (3)节点本身变为右孩子的左子树

整个操作流程如动图 5.1.2 所示。


节点的右孩子替代此节点位置 —— 节点 66 的右孩子是节点 77 ,将节点 77 代替节点 66 的位置
右孩子的左子树变为该节点的右子树 —— 节点 77 的左子树为节点 75,将节点 75 挪到节点 66 的右子树位置
节点本身变为右孩子的左子树 —— 节点 66 变为了节点 77 的左子树
5.2 右旋
右旋操作与左旋类似,操作流程为:

(1)节点的左孩子代表此节点 (2)节点的左孩子的右子树变为节点的左子树 (3)将此节点作为左孩子节点的右子树。


6. AVL树的四种插入节点方式
假设一颗 AVL 树的某个节点为 A,有四种操作会使 A 的左右子树高度差大于 1,从而破坏了原有 AVL 树的平衡性。平衡二叉树插入节点的情况分为以下四种:

| 插入方式 | 描述 | 旋转方式 | | -------- | ------------------------------------------------------- | ------------ | | LL | 在 A 的左子树根节点的左子树上插入节点而破坏平衡 | 右旋转 | | RR | 在 A 的右子树根节点的右子树上插入节点而破坏平衡 | 左旋转 | | LR | 在A的左子树根节点的右子树上插入节点而破坏平衡 | 先左旋后右旋 | | RL | 在 A 的右子树根节点的左子树上插入节点而破坏平衡 | 先右旋后左旋 |

具体分析如下:

6.1 A的左孩子的左子树插入节点(LL)
只需要执行一次右旋即可。


实现代码如下:

//LL型调整函数
//返回:新父节点
Tree LL_rotate(Tree node){
    //node为离操作结点最近的失衡的结点
    Tree parent=NULL,son;
    //获取失衡结点的父节点
    parent=node->parent;
    //获取失衡结点的左孩子
    son=node->lchild;
    //设置son结点右孩子的父指针
    if (son->rchild!=NULL)  son->rchild->parent=node;
    //失衡结点的左孩子变更为son的右孩子
    node->lchild=son->rchild;
    //更新失衡结点的高度信息
    update_depth(node);
    //失衡结点变成son的右孩子
    son->rchild=node;
    //设置son的父结点为原失衡结点的父结点
    son->parent=parent;
    //如果失衡结点不是根结点,则开始更新父节点
    if (parent!=NULL){
        //如果父节点的左孩子是失衡结点,指向现在更新后的新孩子son
        if (parent->lchild==node){
            parent->lchild=son;
        }else{
             //父节点的右孩子是失衡结点
              parent->rchild=son;
        }
     }
    //设置失衡结点的父亲
    node->parent=son;
    //更新son结点的高度信息
    update_depth(son);
    return son;
}

6.2 A的右孩子的右子树插入节点(RR)
只需要执行一次左旋即可。


实现代码如下:

//RR型调整函数
//返回新父节点
Tree RR_rotate(Tree node){
    //node为离操作结点最近的失衡的结点
    Tree parent=NULL,son;
    //获取失衡结点的父节点
    parent=node->parent;
    //获取失衡结点的右孩子
    son=node->rchild;
    //设置son结点左孩子的父指针
    if (son->lchild!=NULL){
          son->lchild->parent=node;
    }
    //失衡结点的右孩子变更为son的左孩子
    node->rchild=son->lchild;
    //更新失衡结点的高度信息
    update_depth(node);
    //失衡结点变成son的左孩子
    son->lchild=node;
    //设置son的父结点为原失衡结点的父结点
    son->parent=parent;
    //如果失衡结点不是根结点,则开始更新父节点
    if (parent!=NULL){
        //如果父节点的左孩子是失衡结点,指向现在更新后的新孩子son
        if (parent->lchild==node){
            parent->lchild=son;
        }else{
            //父节点的右孩子是失衡结点
            parent->rchild=son;
        } 
    }
    //设置失衡结点的父亲
    node->parent=son;
    //更新son结点的高度信息
    update_depth(son);
    return son;
}

   

6.3 A的左孩子的右子树插入节点(LR)
若 A 的左孩子节点 B 的右子树 E 插入节点 F ,导致节点 A 失衡,如图:


A 的平衡因子为 2 ,若仍按照右旋调整,则变化后的图形为这样:


经过右旋调整发现,调整后树仍然失衡,说明这种情况单纯的进行右旋操作不能使树重新平衡。那么这种插入方式需要执行两步操作,使得旋转之后为 原来根结点的左孩子的右孩子作为新的根节点 。

(1)对失衡节点 A 的左孩子 B 进行左旋操作,即上述 RR 情形操作。 (2)对失衡节点 A 做右旋操作,即上述 LL 情形操作。

调整过程如下:

也就是说,经过这两步操作,使得 原来根节点的左孩子的右孩子 E 节点成为了新的根节点。

代码实现:

//LR型,先左旋转,再右旋转
//返回:新父节点
Tree LR_rotate(Tree node){
    RR_rotate(node->lchild);
    return LL_rotate(node);
}

 
6.4 A的右孩子的左子树插入节点(RL)
右孩子插入左节点的过程与左孩子插入右节点过程类似,也是需要执行两步操作,使得旋转之后为 原来根结点的右孩子的左孩子作为新的根节点 。

(1)对失衡节点 A 的右孩子 C 进行右旋操作,即上述 LL 情形操作。 (2)对失衡节点 A 做左旋操作,即上述 RR 情形操作。

也就是说,经过这两步操作,使得 原来根节点的右孩子的左孩子 D 节点成为了新的根节点。

代码实现:

//RL型,先右旋转,再左旋转
//返回:新父节点
Tree RL_rotate(Tree node){
    LL_rotate(node->rchild);
    return RR_rotate(node);
}

      
补充:上述四种插入方式的代码实现的辅助代码如下:

//更新当前深度
void update_depth(Tree node){
    if (node==NULL){
        return;
    }else{
        int depth_Lchild=get_balance(node->lchild); //左孩子深度
        int depth_Rchild=get_balance(node->rchild); //右孩子深度
        node->depth=max(depth_Lchild,depth_Rchild)+1;
    }
}
 
//获取当前结点的深度
int get_balance(Tree node){
    if (node==NULL){
         return 0;
    }
    return node->depth;
}
 
//返回当前平衡因子
int is_balance(Tree node){
    if (node==NULL){
         return 0;
    }else{
         return get_balance(node->lchild)-get_balance(node->rchild); 
    }
}

6.5 小总结
在所有的不平衡情况中,都是按照先 寻找最小不平衡树,然后 寻找所属的不平衡类别,再 根据 4 种类别进行固定化程序的操作。
LL , LR ,RR ,RL其实已经为我们提供了最后哪个结点作为新的根指明了方向。如 LR 型最后的根结点为原来的根的左孩子的右孩子,RL 型最后的根结点为原来的根的右孩子的左孩子。只要记住这四种情况,可以很快地推导出所有的情况。
维护平衡二叉树,最麻烦的地方在于平衡因子的维护。建议读者们根据小吴提供的图片和动图,自己动手画一遍,这样可以更加感性的理解操作。
7. AVL树的四种删除节点方式
AVL 树和二叉查找树的删除操作情况一致,都分为四种情况:

(1)删除叶子节点 (2)删除的节点只有左子树 (3)删除的节点只有右子树 (4)删除的节点既有左子树又有右子树

只不过 AVL 树在删除节点后需要重新检查平衡性并修正,同时,删除操作与插入操作后的平衡修正区别在于,插入操作后只需要对插入栈中的弹出的第一个非平衡节点进行修正,而删除操作需要修正栈中的所有非平衡节点。

删除操作的大致步骤如下:

以前三种情况为基础尝试删除节点,并将访问节点入栈。
如果尝试删除成功,则依次检查栈顶节点的平衡状态,遇到非平衡节点,即进行旋转平衡,直到栈空。
如果尝试删除失败,证明是第四种情况。这时先找到被删除节点的右子树最小节点并删除它,将访问节点继续入栈。
再依次检查栈顶节点的平衡状态和修正直到栈空。
对于删除操作造成的非平衡状态的修正,可以这样理解:对左或者右子树的删除操作相当于对右或者左子树的插入操作,然后再对应上插入的四种情况选择相应的旋转就好了。

总结
AVL 的旋转问题看似复杂,但实际上如果你亲自用笔纸操作一下还是很好理解的。


                        
原文链接:https://blog.csdn.net/kexuanxiu1163/article/details/103080901

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

指针的进阶(C语言)(下)

目录 4、数组参数、指针参数传参 4.1一维数组传参 4.2二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5、函数指针 6、函数指针数组 7、指向函数指针数组的指针 8、回调函数 总结 续上篇 4、数组参数、指针参数传参 在写代码的时候难免把【数组】或者【指针】传给…

MySQL 多表操作

一.多表关系 1.一对一关系 一个学生只有一张身份证;一张身份证只能对应一个学生。 在任一表中添加外键,指向另一方主键,确保一对一关系。 一般一对一关系很少见,遇到一对一关系的表最好合并。 2.一对多/多对一关系 一个部门…

ArcgisForJS如何访问Arcgis Server?

文章目录 0.引言1.准备ArcGIS相关工具2.创建含有ArcSDE地理数据库的MXD文件3.注册ArcSDE地理数据库4.发布数据到Arcgis Server5.ArcgisForJS访问ArcGIS Server数据 0.引言 ArcGIS API for JavaScript 是一个用于在Web和移动应用程序中创建交互式地图和地理空间分析应用的库。它…

抽象工厂模式 Abstract Factory

1.模式定义: 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类 2. 应用场景: 程序需要处理不同系列的相关产品,但是您不希望它依赖于这些产品的 具体类时, 可以使用抽象工厂 3.优点: 1.可以确信你从工厂得到的产品彼…

恒峰-智能高压森林应急消防泵:安全护林新利器

随着科技的发展,人类对自然资源的保护意识日益增强。森林作为地球上最重要的生态系统之一,对于维护生态平衡、净化空气、保持水源等方面发挥着举足轻重的作用。然而,森林火灾却时常威胁着这片绿色家园。为了更好地保护森林资源,智…

D5020——外围元件少,内含压缩器和扩展器静噪电路,可应用在1.5V立体声耳机上,响应时间可调

D5020是一块增益可调 的压缩、扩展电路。它有两个通道组成,一个通道作扩展用,另一个通道能作压缩或扩展用。电路内部含有小信号全波整流、检测信号的大小,用于调节输入或反馈通道的增益大小。含有温度特性较好的带隙精密基准源,静…

二.西瓜书——线性模型、决策树

第三章 线性模型 1.线性回归 “线性回归”(linear regression)试图学得一个线性模型以尽可能准确地预测实值输出标记. 2.对数几率回归 假设我们认为示例所对应的输出标记是在指数尺度上变化,那就可将输出标记的对数作为线性模型逼近的目标,即 由此&…

五种多目标优化算法(NSWOA、MOJS、MOAHA、MOPSO、NSGA2)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 1.1NSWOA 1.2MOJS 1.3MOAHA 1.4MOPSO 1.5NSGA2 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数(zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3)&#xff0…

上进计划 | Python爬虫经典实战项目——电商数据爬取!

电商数据采集之——电商数据爬虫|电商数据采集API接口 电商数据爬虫背景 在如今这个网购风云从不间歇的时代,购物狂欢持续不断,一年一度的“6.18年中大促”、“11.11购物节”等等成为了网购电商平台的盛宴。在买买买的同时,“如何省钱&#…

昇腾ACL应用开发之模型转换ATC

一.前提条件 在前面的章节中我们已经安装了包含模型转换的套件包CANN-TOOLKIT,默认的安装路径会在/usr/local/Ascend里面,我们将该套件所需要的东西加入到环境变量中以便我们调用: 将source /usr/local/Ascend/ascend-toolkit/set_env.sh加入…

【鸿蒙系统学习笔记】TypeScript开发语言

一、背景 HarmonyOS 应用的主要开发语言是 ArkTS,它由 TypeScript(简称TS)扩展而来,在继承TypeScript语法的基础上进行了一系列优化,使开发者能够以更简洁、更自然的方式开发应用。值得注意的是,TypeScrip…

力扣 面试题 05.06. 整数转换

思路: 牵扯到二进制数,基本上要考虑位运算符,相关知识可以见http://t.csdnimg.cn/fzts7 之前做过类似的题目,大致思路就是先用按位异或^找出不同位,再用n&(n-1)计算出不同位的个数&#x…

nuxt项目搭建

1.先下载nuxt脚手架 yarn create nuxt-app <项目名>&#xff0c;记得安装完项目&#xff0c;npm i,下载node包 目录介绍 components 存放组件分别是头部&#xff08;包含导航&#xff09;和底部 layouts 页面布局&#xff0c;实现一个页面整体架构规则&#xff0c;头…

Sora 全网最全资料

大家好,本资料库是全网集体智慧的结晶,通过这个资料库,我们希望能够为读者提供一个全方位、多角度了解和研究Sora大模型的平台。每一部分都旨在深入探讨Sora大模型的不同方面,从技术细节到社会影响,再到未来展望,以确保读者能够获得最全面的信息和洞见。 📁一. 概念和…

yolov5导出onnx转engine推理

yolov5导出注意事项 配置 需要提供配置文件和权重文件&#xff0c;不然导出模型不能正常推理。 默认提供检测头。 ModuleNotFoundError: No module named ‘tensorrt’安装TensorRT-python发现报错 由于ModuleNotFoundError: No module named ‘tensorrt’安装TensorRT-pyt…

Android14 InputManager-InputManagerService环境的构造

IMS分为Java层与Native层两个部分&#xff0c;其启动过程是从Java部分的初始化开始&#xff0c;进而完成Native部分的初始化。 □创建新的IMS对象。 □调用IMS对象的start&#xff08;&#xff09;函数完成启动 同其他系统服务一样&#xff0c;IMS在SystemServer中的ServerT…

不要抱怨,不如抱 Java 运算符吧 (1)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

mplfinance 使用make_addplot做复杂股票走势图

mplfinance 使用make_addplot做复杂股票走势图 1.代码 import talib as tb import pandas as pd import mplfinance as mpfimport matplotlib.pyplot as pltplt.rcParams[font.sans-serif][simHei] # 以黑体显示中文 plt.rcParams[axes.unicode_minus]False # 解决保存图像符…

Meta 发布 MMCSG (多模态智能眼镜对话数据集)

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

LeetCode 热题 100 Day01

哈希模块 哈希结构&#xff1a; 哈希结构&#xff0c;即hash table&#xff0c;哈希表|散列表结构。 图摘自《代码随想录》 哈希表本质上表示的元素和索引的一种映射关系。 若查找某个数组中第n个元素&#xff0c;有两种方法&#xff1a; 1.从头遍历&#xff0c;复杂度&#xf…