AVL 树(自平衡二叉搜索树) 介绍

AVL 树(自平衡二叉搜索树) 介绍

  1. 前言

在介绍二叉搜索树的章节中提到,二叉搜索树可能退化为线性链表,失去作为二叉树的各种优势。那么过程中需要维持二叉树的形式,同时左右子树的深度差异可控,如果能实现这两个条件,那么我们称这样的树为自平衡二叉搜索树,由于这类数最早由苏联数学家 Georgy Adelson Velsky 和Landis共同提出,又称之为AVL树。

  1. AVL树特征

AVL树的每个结点都需要维护平衡因子,平衡因子的取值范围为-1,0,1,如果所有结点的平衡因子的取值范围都落在-1,0,1范围内,就称这棵树为自平衡二叉搜索树(AVL树)。

观察一个具体例子,下面一棵树就属于一颗AVL 树,因为每个结点的平衡因子都落在-1,0,1范围之内。

在这里插入图片描述

下面这棵树就不属于AVL树,因为有的结点的平衡因子为2.

在这里插入图片描述

  1. AVL树旋转

AVL树可以在元素插入过程中进行旋转处理,确保所有节点的平衡因子落在合理范围之内,如果由于元素插入导致节点的平衡因子落在合理范围之外,那么就需要通过不同模式的旋转操作实现再平衡。值得一提的是,所有的旋转和再平衡操作都发生在递归插入完成之后,也就是从叶子节点开始,不断向上进行不同的调整。

3-1. 左旋操作

左旋操作的对象涉及到两个结点X和Y,当X结点的平衡因子的值超过限制范围的时候,而且右子树比左子树要“重”,这是后就需要把Y作为新的根节点,相当于对X施加左旋的基本操作,操作完成后,整个树满足AVL树的基本要求。

旋转前:

在这里插入图片描述

左旋后,Y将作为子树的新根节点,同时满足平衡因子满足AVL树的基本要求。

在这里插入图片描述

3-2 右旋操作

在这里插入图片描述

右旋操作与左旋操作相反,如上图所示,需要以对Y进行右旋操作,旋转操作之后,二叉树的平衡因子满足AVL树的基本条件和要求,重新回归到AVL树的属性。

3-3 先左旋后右旋

先左旋后右旋的操作会涉及到三个结点,结点Z,X和Y,操作需要自下而上,先对X-Y结点组合施加左旋操作,

操作前:

在这里插入图片描述

第一步,左旋操作,

在这里插入图片描述

第二步,右旋操作

在这里插入图片描述

操作完成后,二叉树重新取得平衡,回归AVL树的属性。

3-4 先右旋再左旋

先右旋再左旋也涉及到三个结点,操作顺序也是自下而上(利用递归回退前的信息属性进行操作),先对靠近底部的子树进行右旋操作,而后再进行左旋处理。

先右旋处理

在这里插入图片描述

后左旋处理

在这里插入图片描述

  1. AVL树的平衡因子和结点高度

平衡因子定义为左子树高度和右子树高度差,由于在旋转过程中需要对平衡因子进行维护和更新,所以需要动态求出每个结点的子树在插入或删除后,本身的平衡因子的变化。结点的平衡因子取决于左右子树的高度差,所以如果需要更新结点的平衡因子,那么首先首先求解本节点两个子树的高度。
b a l a n c e _ f a c t o r = h e i g h t ( l e f t _ s u b t r e e ) − h e i g h t ( r i g h t _ s u b t r e e ) ; balance\_factor=height(left\_subtree)-height(right\_subtree); balance_factor=height(left_subtree)height(right_subtree);

  1. AVL树的插入操作

AVL树的插入操作与普通二叉树插入操作基本相同,唯一不同点在于,插入完成后,需要对节点自底而上进行平衡因子的更新及相应的转动操作。这些反转操作必须在插入完成后才能进行,插入前无法进行更新,这就类似单个递归的后续操作,当递归完成后,然后再对相应的信息进行相关操作。

在进行插入操作之前,需要进行对相关的辅助函数进行编写。其中get_height(Node *node)函数返回节点当前的高度,如果节点没有左右子树,节点高度定义为1。

typedef struct Node
{
    int key;
    struct Node *lchild;
    struct Node *rchild;
    int height;
}Node;


int get_height(Node *node)
{
    if(node==NULL)
    {
        return 0;
    }
    else
    {
        return node->height;
    }
}

根据上面的公式,可以求出每个节点的平衡因子,get_balance_factor(Node *node)函数的作用为求出某个节点的平衡因子。

int get_balance_factor(Node *node)
{
    if(node==NULL)
    {
        return 0;
    }
    else
    {
        return (get_height(node->lchild) - get_height(node->rchild));
    }
}

如果要得到某个节点的后继节点,那么就需要使用函数find_successor(Node *node)进行求解,然后返回某个相关的节点。

Node *find_successor(Node *node)
{
    Node *p;

    if(node==NULL)
    {
        return NULL;
    }

    p=node->rchild;

    while(p && p->lchild)
    {
        p=p->lchild;
    }

    return p;
}

如果需要创建新的节点,那么就需要采用函数make_node,创建新的节点,然后返回创建的节点即可。

Node *make_node(int key)
{
    Node *node;

    node=(Node*)malloc(sizeof(Node));
    node->height=1;
    node->lchild=node->rchild=NULL;
    node->key=key;

    return node;
}

左旋操作

Node *left_rotation(Node *node)
{
  	Node *rc;
  	Node *new_node;

  	rc=node->rchild;
  	node->rchild=rc->lchild;
  	rc->lchild=node; //node =rc;
  	new_node=rc;

    // from the bottom to top
  node->height=max(get_height(node->lchild),get_height(node->rchild))+1;

  new_node->height = max(get_height(new_node->lchild), get_height(new_node->rchild)) + 1;

  return new_node;
}

右旋操作

Node *right_rotation(Node *node)
{
    Node *lc;
    Node *new_node;

    lc=node->lchild;
    node->lchild=lc->rchild;
    lc->rchild=node;
    new_node=lc;

    //from the bottom to top
    node->height = max(get_height(node->lchild), get_height(node->rchild)) + 1;

    new_node->height = max(get_height(new_node->lchild), get_height(new_node->rchild)) + 1;

    return new_node;
}

节点插入的函数分析,插入节点的函数当中,出现node->lchild=insert_node(node->lchild,key)表达是,这个表达式的作用是把前一个栈弹出的值赋给node->lchild即可,所以它不需要返回值,只需要赋值即可。

Node *insert_node(Node *node, int key)
{
    int bf; //balance factor;
    if(node==NULL)
    {
        return make_node(key); //create the new node
    }

    if(key < node->key)
    {
        node->lchild=insert_node(node->lchild,key);
    }
    else if(key > node->key)
    {
        node->rchild=insert_node(node->rchild,key);
    }
    else
    {
        return node; //termination condition had been reached;
    }

    //update the balance factor of each node and rebalance the AVL tree
    node->height=max(get_height(node->lchild),get_height(node->rchild))+1;
    bf=get_balance_factor(node);

    if(bf>1 && key < node->lchild->key)
    {
        return right_rotation(node);
    }

    if(bf<-1 && key >node->rchild->key)
    {
        return left_rotation(node);
    }

    if(bf>1 && key > node->lchild->key)
    {
        node->lchild=left_rotation(node->lchild);
        return right_rotation(node);
    }

    if(bf<-1 && key < node->rchild->key)
    {
        node->rchild=right_rotation(node->rchild);
        return left_rotation(node);
    }
    

    return node; //general root node;
}

节点删除函数分析,节点的删除分为三类情况:

如果节点的左子树或右子树为空,那么直接把其右子树或左子树赋给当前节点即可。如果左右子树均不为空,那么就需要找到此节点的后继节点,用后继节点的值替换待删除节点的值,最后把后继节点删除即可,后继节点可以为叶子节点也可以为中间的某个节点。

具体的实现函数为

Node *delete_node(Node *root, int key)
{
    //Deleting the node is the best algorithm we've ever had, it deserves learning
    
    Node* temp;
    int bf;
    
    
    if(root==NULL)
    {
        return root; //failure to find the target node
    }

    if(key < root->key)
    {
        root->lchild=delete_node(root->lchild,key);
    }
    else if(key > root->key)
    {
        root->rchild=delete_node(root->rchild,key);
    }
    else
    {
        if(root->lchild==NULL || root->rchild==NULL)
        {
            temp=(root->lchild?root->lchild:root->rchild);

            if(temp==NULL)
            {
                temp=root;
                root=NULL;
            }
            else
            {
                *root=*temp; //it will equal to left child or righ child;
            }

            free(temp);
            temp=NULL;
        }
        else
        {
            temp=find_successor(root);
            root->key=temp->key;

            //Highlight these application
            root->rchild=delete_node(root->rchild,temp->key); //delete the leaf key
        }
    }

    if(root==NULL) //key steps, if the last node is empty, then return empty
    {
        return root;
    }

    root->height=max(get_height(root->lchild),get_height(root->rchild))+1;

    bf=get_balance_factor(root);

    if(bf>1 &&get_balance_factor(root->lchild)>=0) //>=0;
    {
        return right_rotation(root);
    }

    if (bf > 1 && get_balance_factor(root->lchild) < 0)
    {
        root->lchild=left_rotation(root->lchild);
        return right_rotation(root);
    }


    if(bf<-1 && get_balance_factor(root->rchild)<=0)//<=0
    {
        return left_rotation(root);
    }

    if (bf < -1 && get_balance_factor(root->rchild) >0)
    {
        root->rchild=right_rotation(root->rchild);
        return left_rotation(root);
    }

    return root;
}
  1. 小结

要理解AVL算法,关键和核心是理解递归后的处理逻辑,递归后如果需要对子树进行旋转,那么旋转后直接返回即可,否则则需要原来的节点。这里面涉及到比较复杂的递归利用。

不同于严蔚敏《数据结构》,这里实现的算法比较直观,没有《数据结构》里面taller对树的高度的判断,如果有时间将分析严蔚敏版本的AVL树,里面的代码简洁,但是对于删除操作实现,难度很高。

参考资料

https://www.programiz.com/dsa/avl-tree

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

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

相关文章

音视频 FFmpeg

文章目录 前言视频编解码硬件解码(高级)软解码(低级)软、硬解码对比视频解码有四个步骤Android 系统中编解码器的命名方式查看当前设备支持的硬解码 基础知识RGB色彩空间常见的格式对比YUV索引格式分离RGB24像素数据中的R、G、B分量 BMP 文件格式格式组成像素排列顺序RGB24格式…

autosar软件分层架构组成--汽车电子

介绍 autosar是汽车软件协会制定的一套软件标准 本文章所有图片来源于网络 一、分层架构 分层&#xff1a;3层 1.上层应用层&#xff08;Application Layer&#xff09; 2.中间件RTE(Runtime Environment) 3.下层的基础软件&#xff08;Basic Software&#xff09; 中间件R…

倾斜摄影超大场景的三维模型轻量化纹理压缩的关键技术

倾斜摄影超大场景的三维模型轻量化纹理压缩的关键技术 倾斜摄影超大场景的三维模型轻量化处理中纹理压缩是轻量化处理的重要手段之一&#xff0c;可以在保证模型真实感的前提下&#xff0c;减小数据体积、降低传输带宽和提高渲染性能。以下是几个关键的纹理压缩技术&#xff1a…

沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置

目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置 CH32V208 CH32V208系列是沁恒32位RISC-V中比较新的一个系列, 基于青稞RISC-V4C内核, 最高144MHz主频, 64KB SRAM&#xff0c;128KB Flash, 供电电压2.5/3.3V. 这个型号的特点: 除了特有的硬件堆栈区、…

深度学习第J8周:Inception v1算法实战与解析

目录 一、Inception v1 1.简介 2. 算法结构 二、pytorch代码复现1.前期准备 2.代码复现 3.训练运行 3.2指定图片进行预测 三、总结 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学啊* 所有&#xff09; &#x1f356; 作…

Linux:网络基础1

网络协议分层 所有网络问题&#xff0c;本质都是通信距离变长了&#xff0c;为了尽可能减少通信成本&#xff0c;定制了协议。 协议分层的优势&#xff1a; 软件设计方面的优势 - 低耦合 一般我们的分层依据: 功能比较集中&#xff0c;耦合度比较高的模块-- 一层 &#xff0c…

2023五一数学建模A题完整思路

已更新五一数学建模A题思路&#xff0c;文章末尾获取&#xff01; A题完整思路&#xff1a; A题是一个动力学问题&#xff0c;需要我们将物理学概念运用到实际生活中&#xff0c;我们可以先看题目 问题1&#xff1a; 假设无人机以平行于水平面的方式飞行&#xff0c;在空中投…

代码审计笔记之开篇

思想 代码审计是从软件测试发展而来&#xff0c;早起一般采用常规软件测试与渗透测试的手段来发现源码漏洞&#xff0c;但是随着软件规模的越来越大&#xff0c;架构越来越复杂&#xff0c;安全漏洞和后门也越来越多越来越隐蔽&#xff0c;这使得传统的软件测试方法很难检出源…

达梦数据库中注释的使用

在管理规模较大的数据库时&#xff0c;我们往往需要面对大量的表与视图&#xff0c;与此同时在表与视图中可能会存在着许多的字段&#xff0c;让人难以迅速分辨&#xff0c;不利于对于数据库对象的管理。除了在命名时&#xff0c;对于有意义的表、视图及列&#xff0c;应尽量赋…

你可能需要的IDEA-Java开发插件

Idea开发插件 Alibaba Cloud AI Coding Assistant 阿里云智能编码插件&#xff08;Alibaba Cloud AI Coding Assistant&#xff09;是一款AI编程助手&#xff0c;它提供代码智能补全和代码示例搜索能力&#xff0c;帮助你更快更高效地写出高质量代码。 让我觉得比较有意思的…

CentOS防火墙的常用快捷命令

CentOS是免费开源的Linux发行版之一,它兼容RHEL并由社区进行维护,大多数美国服务器提供对该系统支持。在使用CentOS系统时,您需要了解一些常用命令,比如开启、查看、关闭防火墙等。本文将介绍下CentOS防火墙的常用命令。 CentOS是一种面向企业级服务器环境的Linux发行版,…

直击德国PLS展,联诚发倾力打造沉浸式视觉盛宴!

当地时间4月25-28日&#xff0c;备受关注的2023德国法兰克福国际专业灯光音响展ProlightSound&#xff08;以下简称“PLS展”&#xff09;在德国法兰克福盛大召开。联诚发携多款创新产品及多领域的应用解决方案精彩亮相&#xff0c;为全球客户打造沉浸式视觉盛宴&#xff0c;展…

JavaScript详解

一、前置知识 1.1第一个JS程序 JavaScript 代码可以嵌入到 HTML 的 script 标签中。 1.2JS书写格式 1.2.1行内样式 直接嵌入到html元素内部 1.2.2内嵌格式 1.2.3外部格式 注意这种情况下&#xff0c;script标签中间不能写任何代码&#xff0c;必须空着&#xff0c;就算…

java内存占用过大分析,mat内存快照分析

背景 最近功能模块上线后&#xff0c;生产内存占用显著提升&#xff0c;查看gc日志发现年轻代频繁从2G回收到60M左右&#xff0c;猜测是在方法中频繁创建大对象导致&#xff0c;由于一时间无法通过review代码找出问题所在&#xff0c;只好将生产jvm内存快照dump后通过java mem…

HCIA-RS实验-STP和RSTP(2)

接上一篇文章&#xff1b;其他的不多说&#xff0c;新建一个新的配置设备&#xff1b;如果接上一个实验的配置的话&#xff0c;建议先把所有配置删除后再执行&#xff1b;新的拓扑也与上一个实验一致&#xff1b; 目录 创建新配置 配置RSTP 查看stp版本 配置边缘端口 …

深度学习 GNN图神经网络(四)线性回归之ESOL数据集水溶性预测

线性回归之ESOL数据集水溶性预测 一、前言二、ESOL数据集三、加载数据集四、数据拆分五、构造模型六、训练模型七、测试结果八、分类问题参考文献 一、前言 本文旨在使用化合物分子的SMILES字符串进行数据模型训练&#xff0c;对其水溶性的值进行预测。 之前的文章《深度学习…

vue - pc端实现对div的拖动功能

实现对div的拖动功能&#xff0c;需要先要知道以下的一些原生事件和方法&#xff1b; 1&#xff0c;事件: 方法描述onmousedown鼠标按钮被按下onmousemove鼠标被移动onmouseup鼠标按键被松开 2&#xff0c;方法: 方法描述event.clientX返回当事件被触发时鼠标指针相对于浏览…

02 【Sass语法介绍-变量】

sass有两种语法格式Sass(早期的缩进格式&#xff1a;Indented Sass)和SCSS(Sassy CSS) 目前最常用的是SCSS&#xff0c;任何css文件将后缀改为scss&#xff0c;都可以直接使用Sassy CSS语法编写。 所有有效的 CSS 也同样都是有效的 SCSS。 Sass语法介绍-变量 1.前言 Sass …

【VM服务管家】VM4.0平台SDK_2.5 全局工具类

目录 2.5.1 全局相机&#xff1a;全局相机设置参数的方法2.5.2 全局相机&#xff1a;获取全局相机列表的方法2.5.3 全局通信&#xff1a;通信管理中设备开启状态管理2.5.4 全局通信&#xff1a;接收和发送数据的方法2.5.5 全局变量获取和设置全局变量的方法 2.5.1 全局相机&…

2023-4-27-深入理解C++指针类型间强制转换

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…