数据结构-AVL树

news2024/12/24 20:43:29

目录

什么是 AVL 树

ASL 度量查找效率

结构体定义

平衡调整

调整类型

左旋和右旋

右旋

左旋

左、右平衡调整

左平衡调整

右平衡调整

插入数据

模拟建立 AVL 树


什么是 AVL 树

        二叉排序树的形状取决于数据集,当二叉树的高度越小、结构越合理,搜索的性能就越好,时间复杂度 O(log2n)。G. M. Adelson-Velsky 和 E. M. Landis 在1962年的论文《An algorithm for the organization of information》中发表了一种名为 AVL 树的数据结构,它就能很好地解决这个问题。AVL 树具有以下 2 个性质:

  1. 左子树和右子树的深度之差的绝对值不超过 1;
  2. 左子树和右子树通通都是 AVL 树。

        其中为了度量左右子树的深度之差,我们引入平衡因子 (BF)"的概念。例如下面的二叉搜索树的平衡因子为:

        对于一棵 AVL 树,里面的所有结点的平衡因子只能取值于:-1、0、1


ASL 度量查找效率

        为了更好地理解 AVL 树,请认真观察下面 2 个二叉搜索树,我们发现第二个二叉搜索树是 AVL 树,树的高度更小,查找的比较次数也更少,效率更高。


        现在计算一下该 AVL 树的 ASL:

ASL(成功):(1 + 2 × 2 + 3 × 4) ÷ 6 = 17/6
ASL(失败):4 × 8 ÷ 8 = 4

        和二叉搜索树对比,发现无论是成功还是失败的 ASL,AVL 树的都较小,说明效率更高。


结构体定义

typedef struct BiTNode
{
      int data;
      int bf;  //平衡因子
      struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

平衡调整

调整类型

        由于二叉搜索树的结点是一个一个插入的,在插入时可能会造成结点的平衡因子的绝对值超过 1。造成失衡一共有 4 种情况:RR 型、LL 型、RL 型、LR 型,如图所示:

        虽然有 4 种情况,但是需要遵循的原则是一样的——在尽可能减小高度的前提下,保持二叉搜索树的性质。下面就看一下 4 种情况的调整示意图,不难发现都是遵循这个原则进行调整的。


        需要注意的是,当有多个结点失衡时,需要选择最小失衡子树来调整。


左旋和右旋

右旋

        右旋可以形象地理解为把最上面的结点掰下来,这种操作称之为“右旋”。右旋操作后指向新的根结点,即操作之前的根结点的左结点。

void R_Rotate(BiTree &T)
{ 
      BiTree L;      //L 表示 T 的左子树
      L = T->lchild;    //L 指向 T 的左子树 
      T->lchild = L->rchild;      //T 的左子树更改为 T 的左子树的右子树  
      L->rchild = T;      //T 的左子树的右子树修改为 T
      T = ptr;      //根结点修改为右旋之前 T 的左子树
}

左旋

        对于 RR 型的调整,可以形象地理解为把最上面的结点掰下来,这种操作称之为“左旋”。左旋操作后指向新的根结点,即操作之前的根结点的右结点。

void L_Rotate(BiTree &T)
{ 
      BiTree R;      //R 表示 T 的右子树
      R = T->rchild;        //R 指向 T 的右子树  
      T->rchild = R->lchild;      //T 的右子树更改为 T 的右子树的左子树  
      R->lchild = T;      //T 的右子树的左子树修改为 T
      T = R;      //根结点修改为左旋之前 T 的右子树
}

左、右平衡调整

左平衡调整

        这个函数为左平衡旋转,将 RL 型和 LL 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。

void LeftBalance(BiTree &T)
{ 
    BiTree L;      //L 表示 T 的左子树
    BiTree Lr;      //Lr 表示 L 的右子树

    L = T->lchild;      //L 指向 T 的左结点 
    switch(L->bf)      //根据 T 的左子树的 BF 作相应平衡处理
    {
        case 1:          //LL 型,新结点插入在 T 的左结点的左子树
        {
            T->bf = L->bf = 0;      //修正 BF 均为 0
            R_Rotate(T);      //右旋操作
            break;
        }
        case -1:          //RL 型,新结点插入在 T 的左结点的右子树,要做双旋操作
        {
            Lr = L->rchild;       //Lr 指向示 L 的右结点
            switch(Lr->bf)      //修正 T 及其左结点的平衡因子
            { 
                case 1:            //Lr 平衡因子为 1
                {
                    T->bf = -1;
                    L->bf = 0;
                    break;
                }
                case 0:            //Lr 平衡因子为 0
                {
                    T->bf = L->bf = 0;
                    break;
                }
                case -1:            //Lr 平衡因子为 -1
                {
                    T->bf = 0;
                    L->bf = 1;
                    break;
                }
            }
            Lr->bf = 0;      //修正 Lr 平衡因子为 0
            L_Rotate(T->lchild);       //对 T 的左子树作左旋操作  
            R_Rotate(T);      //对 T 作右旋操作
            break;
        }
    }
}

右平衡调整

        这个函数为右平衡旋转,将 LR 型和 RR 型进行判断和操作。需要考虑涉及结点所连接的子树,对每个结点的 BF 都进行修正。

void RightBalance(BiTree &T)
{ 
    BiTree R;      //R 表示 T 的右子树
    BiTree Rl;      //Rl 表示 R 的左子树

    R = T->rchild;      //R 指向 T 的右结点 
    switch(R->bf)      //根据 T 的右子树的 BF 作相应平衡处理
    {
        case -1:          //RR 型,新结点插入在 T 的右结点的右子树
        {
            T->bf = R->bf = 0;      //修正 BF 均为 0
            L_Rotate(T);      //左旋操作
            break;
        }
        case 1:          //LR 型,新结点插入在 T 的右结点的左子树,要做双旋操作
        {
            Rl = R->lchild;       //Rl 指向 R 的左结点
            switch(Rl->bf)      //修正 T 及其右结点的平衡因子
            { 
                case -1:            //Rl 平衡因子为 -1
                {
                    T->bf = 1;
                    R->bf = 0;
                    break;
                }
                case 0:            //Rl 平衡因子为 0
                {
                    T->bf = R->bf = 0;
                    break;
                }
                case 1:            //Rl 平衡因子为 1
                {
                    T->bf = 0;
                    R->bf = -1;
                    break;
                }
            }
            Rl->bf = 0;      //修正 Rl 平衡因子为 0
            R_Rotate(T->rchild);       //对 T 的右子树作右旋操作  
            L_Rotate(T);      //对 T 作左旋操作
            break;
        }
    }
}

插入数据

        要插入一个数据,首先需要判断这个数据是否已存在,若在 AVL 树中不存在要插入的数据,则执行插入操作。函数将通过递归找到合适的插入位置,如果在插入时出现失去平衡的情况,要进行对应的平衡旋转处理。

bool InsertAVL(BiTree &T, int e, bool &taller) {
    // taller 表示树的高度是否发生变化
    if (T == NULL) { // 若传入的 T 为空树,将创建根结点
        T = new BiTNode;
        T->data = e;
        T->bf = 0;
        T->lchild = T->rchild = NULL;
        taller = true; // 表示树的高度是否发生变化
    } else {
        if (e == T->data) { // 和 e 有相同数据的结点已存在
            taller = false;
            return false;
        }
        if (e < T->data) { // 进入左子树搜索插入位置
            if (InsertAVL(T->lchild, e, taller) == false) { // 结点已存在
                return false;
            }
            if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
                switch (T->bf) { // 检查 T 的 BF 判断是否调整
                    case 1: // 原本左子树比右子树高,需要进行左平衡处理
                        LeftBalance(T); // 左平衡处理
                        taller = false;
                        break;
                    case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
                        T->bf = 1;
                        taller = true;
                        break;
                    case -1: // 原本右子树比左子树高,仍保持平衡,修正 BF
                        T->bf = 0;
                        taller = false;
                        break;
                }
            }
        } else { // 进入右子树搜索插入位置
            if (InsertAVL(T->rchild, e, taller) == false) { // 结点已存在
                return false;
            }
            if (taller == true) { // 数据插入 T 的左子树中并且高度发生变化
                switch (T->bf) { // 检查 T 的 BF 判断是否调整
                    case 1: // 原本右子树比左子树高,仍保持平衡,修正 BF
                        T->bf = 0;
                        taller = false;
                        break;
                    case 0: // 原本左、右子树等高,仍保持平衡,修正 BF
                        T->bf = -1;
                        taller = true;
                        break;
                    case -1: // 原本右子树比左子树高,需要进行右平衡处理
                        RightBalance(T);
                        taller = false;
                        break;
                }
            }
        }
    }
    return true;
}

模拟建立 AVL 树

        按照整数序列 {4,5,7,2,1,3,6} 依次插入的顺序构造相应平衡二叉树。
        首先结点 4 加入 AVL 树成为根结点。

        结点 5 加入 AVL 树。

        结点 7 加入 AVL 树,此时结点 4 的平衡因子为 -2,需要进行调整。


        进行 RR 型调整。

        结点 2 加入 AVL 树。

        结点 1 加入 AVL 树,此时结点 4 的平衡因子为 2,需要进行调整。

        进行 LL 型调整。

        结点 3 加入 AVL 树,此时结点 5 的平衡因子为 2,需要进行调整。

        进行 LR 型调整。

        结点 6 加入 AVL 树,此时结点 5 的平衡因子为 -2,需要进行调整。

        进行 RL 型调整。

        到此为止,AVL 树建立完毕。完整的代码实现如下:

#include <stdio.h>
#include <stdlib.h>

// AVL树的节点结构
typedef struct BiTNode {
    int data;
    int bf; // 平衡因子
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

// 右旋转
void R_Rotate(BiTree *P) {
    BiTree L;
    L = (*P)->lchild;
    (*P)->lchild = L->rchild;
    L->rchild = (*P);
    *P = L;
}

// 左旋转
void L_Rotate(BiTree *P) {
    BiTree R;
    R = (*P)->rchild;
    (*P)->rchild = R->lchild;
    R->lchild = (*P);
    *P = R;
}

// 左平衡旋转处理
void LeftBalance(BiTree *T) {
    BiTree L, Lr;
    L = (*T)->lchild;
    switch (L->bf) {
        case 1:
            (*T)->bf = L->bf = 0;
            R_Rotate(T);
            break;
        case -1:
            Lr = L->rchild;
            switch (Lr->bf) {
                case 1:
                    (*T)->bf = -1;
                    L->bf = 0;
                    break;
                case 0:
                    (*T)->bf = L->bf = 0;
                    break;
                case -1:
                    (*T)->bf = 0;
                    L->bf = 1;
                    break;
            }
            Lr->bf = 0;
            L_Rotate(&(*T)->lchild);
            R_Rotate(T);
    }
}

// 右平衡旋转处理
void RightBalance(BiTree *T) {
    BiTree R, Rl;
    R = (*T)->rchild;
    switch (R->bf) {
        case -1:
            (*T)->bf = R->bf = 0;
            L_Rotate(T);
            break;
        case 1:
            Rl = R->lchild;
            switch (Rl->bf) {
                case 1:
                    (*T)->bf = 0;
                    R->bf = -1;
                    break;
                case 0:
                    (*T)->bf = R->bf = 0;
                    break;
                case -1:
                    (*T)->bf = 1;
                    R->bf = 0;
                    break;
            }
            Rl->bf = 0;
            R_Rotate(&(*T)->rchild);
            L_Rotate(T);
    }
}

// 插入节点到AVL树中
int InsertAVL(BiTree *T, int key, int *taller) {
    if (!*T) {
        *T = (BiTree)malloc(sizeof(BiTNode));
        (*T)->data = key;
        (*T)->lchild = (*T)->rchild = NULL;
        (*T)->bf = 0;
        *taller = 1;
    } else {
        if (key == (*T)->data) { // 树中已存在相同的节点
            *taller = 0;
            return 0;
        }
        if (key < (*T)->data) { // 插入到左子树
            if (!InsertAVL(&((*T)->lchild), key, taller))
                return 0;
            if (*taller) { // 高度增加了
                switch ((*T)->bf) {
                    case 1:
                        LeftBalance(T);
                        *taller = 0;
                        break;
                    case 0:
                        (*T)->bf = 1;
                        *taller = 1;
                        break;
                    case -1:
                        (*T)->bf = 0;
                        *taller = 0;
                        break;
                }
            }
        } else { // 插入到右子树
            if (!InsertAVL(&((*T)->rchild), key, taller))
                return 0;
            if (*taller) { // 高度增加了
                switch ((*T)->bf) {
                    case 1:
                        (*T)->bf = 0;
                        *taller = 0;
                        break;
                    case 0:
                        (*T)->bf = -1;
                        *taller = 1;
                        break;
                    case -1:
                        RightBalance(T);
                        *taller = 0;
                        break;
                }
            }
        }
    }
    return 1;
}

int main() {
    int count;      // 数据元素的个数
    BiTree T = NULL;
    int taller, flag; // taller 反映 T 的高度是否变化
    int a_num;      // 暂存输入数据

    scanf("%d", &count);
    for (int i = 0; i < count; i++) {
        scanf("%d", &a_num);
        flag = InsertAVL(&T, a_num, &taller); // 向 AVL 树中插入 a_num
    }
    return 0;
}

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

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

相关文章

利用GaussDB的可观测性能力构建故障模型

D-SMART高斯专版已经开发了几个月了&#xff0c;目前主要技术问题都已经解决&#xff0c;也能够初步看到大概的面貌了。有朋友问我&#xff0c;GaussDB不已经有了TPOPS了&#xff0c;为什么你们还要开发D-SMART高斯专版呢&#xff1f; 实际上TPOPS和D-SMART虽然都可以用于Gaus…

Qt客服端开发的组件库

Qt 是一个功能丰富的跨平台 C 应用程序框架&#xff0c;它包含了许多用于不同目的的组件库。以下是一些主要的 Qt 组件库&#xff0c;这些库为开发者提供了广泛的工具和功能&#xff0c;以便构建复杂的应用程序。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&a…

前端页面单元测试最佳策略:全面涵盖逻辑、组件、流程、UI及性能优化测试,全面保障软件应用的质量

页面级别的测试要求我们从更宏观的角度审视应用&#xff0c;不仅关注单个组件的正确性&#xff0c;还要确保组件间的协作无误&#xff0c;以及用户在应用中的完整体验。通过集成测试、E2E测试和场景测试&#xff0c;我们可以更全面地覆盖应用的各种使用情况&#xff0c;提高软件…

实战干货|Spark 在袋鼠云数栈的深度探索与实践

Spark 是一个快速、通用、可扩展的大数据计算引擎&#xff0c;具有高性能、易用、容错、可以与 Hadoop 生态无缝集成、社区活跃度高等优点。在实际使用中&#xff0c;具有广泛的应用场景&#xff1a; 数据清洗和预处理&#xff1a;在大数据分析场景下&#xff0c;数据通常需要…

解决React报错Encountered two children with the same key

当我们从map()方法返回的两个或两个以上的元素具有相同的key属性时&#xff0c;会产生"Encountered two children with the same key"错误。为了解决该错误&#xff0c;为每个元素的key属性提供独一无二的值&#xff0c;或者使用索引参数。 这里有个例子来展示错误是…

学习【Mysql运维篇】这一篇就够了

运维篇 1. 日志1-1. 错误日志1-2. 二进制日志1-3. 查询日志1-4. 慢查询日志 2. 主从复制2-1. 概述2-2. 原理2-3. 搭建 3. 分库分表3-1. 介绍3-2. Mycat概述3-3. Mycat入门3-4. Mycat配置3-5. Mycat分片3-6. Mycat管理及监控 4. 读写分类 1. 日志 1-1. 错误日志 错误日志是MyS…

【hackmyvm】vivifytech靶机

渗透思路 信息收集端口扫描端口服务信息目录扫描爆破hydra--sshgit提权 信息收集 ┌──(kali㉿kali)-[~] └─$ fping -ag 192.168.9.0/24 2>/dev/null 192.168.9.119 --主机 192.168.9.164 --靶机个人习惯&#xff0c;也方便后续操作&#xff0c;将IP地址赋值给一个变…

IDEA 创建Servlet-HelloWorldServlet

servlet 1.创建空项目2.配置web项目3.配置Tomcat4.加载Tomcat包5.创建HelloWorldServlet类6.配置web.xml7.运行get与post请求 1.创建空项目 2.配置web项目 3.配置Tomcat 4.加载Tomcat包 5.创建HelloWorldServlet类 public class controller extends HttpServlet {Override//get…

【Hadoop】MapReduce (五)

MapReduce 入门案例练习 统计文件中每一个单词出现的次数(文件&#xff1a;words.txt)对IP去重(文件&#xff1a;ip.txt) 组件 序列化 - Writable 统计每一个人花费的上行流量、下行流量以及总流量(文件&#xff1a;flow.txt) 在MapReduce中&#xff0c;各个节点之间基本上…

【面试经典 150 | 图的广度优先搜索】最小基因变化

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;广搜 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行回顾…

Java使用SpringBoot和EasyExcel 实现动态数据导出实战

Java使用SpringBoot和EasyExcel 实现动态数据导出实战 1、前言2、【资源地址】3、代码示例(demo)4、目前Java实现数据导出为Excel方式5、依赖6、总结 1、前言 工作中有用到将数据导出为Excel的场景&#xff0c;在此记录下。在日常开发中&#xff0c;Excel文件处理是一项常见的…

部署(Deployment)

Today you’ll be designing your own machine learning project, creating your own dataset, training a model using your data, and finally deploying an application on the web. We’ll be using a particular deployment target called Hugging Face Space with Gradio…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 4月30日,星期二

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年4月30日 星期二 农历三月廿二 1、 气象台&#xff1a;五一假日前期全国大部地区晴好为主&#xff0c;假日后期中东部地区需警惕大范围强降雨。 2、 交通运输部&#xff1a;5月1日0时至5日24时&#xff0c;收费公路小客车免…

通过AI助手实现一个nas定时任务更新阿里云域名解析

一.通过AI助手实现一个ip-domain.py的脚本 起一个Python脚本&#xff0c;ip-domain.py&#xff1b;注意已安装Python3.的运行环境&#xff1b;将下面阿里云相关配置添加&#xff0c;注意这里引用了两个包&#xff0c;requests和alibabacloud_alidns20150109&#xff1b;执行前…

【优质书籍推荐】Vue.js 3.x+Element Plus从入门到精通

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

袁庭新ES系列17节|Spring Data Elasticsearch基础

前言 为了简化对Elasticsearch的操作Spring Data提供了Spring Data Elasticsearch。Spring Data Elasticsearch是Spring Data技术对Elasticsearch原生API封装之后的产物&#xff0c;它通过对原生API的封装&#xff0c;使得程序员可以简单的对Elasticsearch进行各种操作。接下来…

华为 huawei 交换机 配置 MUX VLAN 示例(汇聚层设备)

组网需求 在企业网络中&#xff0c;企业所有员工都可以访问企业的服务器。但对于企业来说&#xff0c;希望企业内部部分员工之间可以互相交流&#xff0c;而部分员工之间是隔离的&#xff0c;不能够互相访问。 如 图 6-4 所示&#xff0c; Switch1 位于网络的汇聚层&#xff0…

STM32F103学习笔记 | 4.STM32F103芯片介绍

STM32F1入门学习将使用STM32F103C8T6开发板最小系统板。小R为什么选择它来入门呢&#xff1f;咳咳~首先&#xff0c;ST官方提供强大且易用的标准库函数&#xff0c;使得开发过程方便快捷&#xff1b;其次&#xff0c;网上的教程资料多也十分详细。所以呢&#xff0c;它对高校学…

SQLite的扩展函数Carray()表值函数(三十八)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite如何处理CSV 虚拟表 下一篇&#xff1a;SQLite—系列文章目录 ​ 1. 概述 Carray()是一个具有单列的表值函数(名为 “value”)和零行或多行。 carray() 中每一行的“值”取自 C 语言数组 由应用程序通过参数绑定提…

【yolov8目标检测部署】TensorRT int8量化

原作者github&#xff1a;https://github.com/xuanandsix/Tensorrt-int8-quantization-pipline/tree/main 改进&#xff1a; 源代码支持的TensorRT版本为7.许多属性已经弃用&#xff1b; 在原有的代码上将支持的TensorRT版本从7改到8. &#xff01;&#xff01;不知道如何安装T…