数据结构--AVL树

一、什么是AVL树

1、AVL树的概念

        二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log_2 n),搜索时间复杂度O(log_2 n)。

2. 平衡因子

平衡因子为结点左右子树的高度差,本文假设平衡因子(bf)=右子树高度-左子树高度

在AVL树中每个结点的平衡因子都是大于等于-1,小于等于1的

二、AVL树的实现

1.AVL树结点结构
template<class K, class V>
struct AVLTreeNode
{
	struct AVLTreeNode* _left;
	struct AVLTreeNode* _right;
	struct AVLTreeNode* _parent; //_parent是为了后续旋转调整方便

	pair<K, V> _kv;//结点数据
	int _bf; //平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0)
	{}
};
2.AVL树的插入
插入流程:

1. 先将结点按二叉搜索树的规则‘’插入AVL树

2. 更新插入结点祖先的平衡因子

        a.如果插入到父亲结点的左边,平衡因子- -

        b.如果插入到父亲结点的右边,平衡因子++

        c.如果父亲的平衡因子==0,说明父亲结点原来的平衡因子一定为1或-1,插入后父亲结点达               到平衡,没有改变高度,插入成功,不需要向上改变祖先的平衡因子

        d.如果父亲的平衡因子为1或-1,说明父亲结点原来的平衡因子一定为0,插入结点后改变了               树的高度,此时需要向上调整祖先结点的平衡因子

        e.如果父亲的平衡因子为2或-2,说明此时不平衡了,需要旋转调整

三、AVL树的旋转

1. 新节点插入较高左子树的左侧---左左:右单旋
if (parent->_bf == -2 && cur->_bf == -1)
//纯粹的左边高
//     *
//    *
//   *

让parent的左指向subLR,如果subLR不为空,更新subLR的父亲结点,让subL的右指向parent,并更新parent与subL的父亲结点,注意parrent结点可能是根节点也可能是一个子树结点,更新subL的父亲节点需要分类讨论。旋转完后,树达到平衡,将parent和subL的平衡因子置为0

void RotateR(Node *parent)
{
    Node *SubL = parent->_left;
    Node *SubLR = SubL->_right;

    parent->_left = SubLR;
    if (SubLR)
        SubLR->_parent = parent;
    SubL->_right = parent;
    Node *ppNode = parent->_parent;
    parent->_parent = SubL;

    if (parent == _root)
    {
        _root = SubL;
        SubL->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = SubL;
        }
        else
        {
            ppNode->_right = SubL;
        }
        SubL->_parent = ppNode;
    }
    parent->_bf = SubL->_bf = 0;
}
2. 新节点插入较高右子树的右侧---右右:左单旋
if (parent->_bf == 2 && cur->_bf == 1)
//纯粹的右边高
//  *
//    *
//      *

与右单旋相类似,让parent的右指向subRL,如果subRL不为空更新subRL的父亲节点,让subR的左指向parent,并更新parent和sunR的父亲节点

void RotateL(Node* parent)
{
    Node* SubR = parent->_right;
    Node* SubRL = SubR->_left;

    parent->_right = SubRL;
    if (SubRL)
        SubRL->_parent = parent;
    SubR->_left = parent;
    Node* ppNode = parent->_parent;
    parent->_parent = SubR;

    if (parent == _root)
    {
        _root=SubR;
        SubR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = SubR;
        }
        else
        {
            ppNode->_right = SubR;
        }
        SubR->_parent = ppNode;
    }
    parent->_bf = SubR->_bf = 0;
}
3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋
if (parent->_bf == -2 && cur->_bf == 1)
//parent结点的左树高,而cur结点的右树高
//    *
//  *
//    *

先以subL为父亲结点进行左单旋,变为纯粹的左边高,再以parent为父亲结点进行右单旋调整,最后调整结点的平衡因子

平衡因子处理分析:

这种情况只有将结点插入到b树或c树后面才会进行左右旋,而左右旋只改变了parent、subL、subLR结点的左右子树,左旋是将b树给了subL结点,右旋是将c树给了parent结点,subLR作为处理树的根结点,故subLR的平衡因子最终为0,所以parent与subL结点平衡因子的关键就是插入结点插入到了b树下还是c树下

  • 如果插入到b树下:subL结点左右子树高度相同,bf=0,parent结点的右子树比左子树高1,bf=1
  • 如果插入到c树下:parent结点左右子树高度相同,bf=0,subL结点的左子树比右子树高1,bf=-1
void RotateLR(Node* parent)
{
    Node* SubL = parent->_left;
    Node* SubLR = SubL->_right;
    int bf = SubLR->_bf;

    RotateL(SubL);
    RotateR(parent);

    if (bf == -1)
    {
        SubL->_bf = 0;
        SubLR->_bf = 0;
        parent->_bf = 1;
    }
    else if (bf == 1)
    {
        SubL->_bf = -1;
        SubLR->_bf = 0;
        parent->_bf = 0;
    }
    else if (bf == 0)
    {
        SubL->_bf = 0;
        SubLR->_bf = 0;
        parent->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋
if (parent->_bf == 2 && cur->_bf == -1)
//parent结点的右树高,而cur结点的左树高
//    *
//      *
//    *

先以subR为父亲结点进行右单旋,变为纯粹的右边高,再以parent为父亲结点进行左单旋调整,最后调整结点的平衡因子

  • 如果插入到c树下:subR结点左右子树高度相同,bf=0,parent结点的左子树比右子树高1,bf=-1
  • 如果插入到b树下:parent结点左右子树高度相同,bf=0,subR结点的右子树比左子树高1,bf=-1
void RotateRL(Node* parent)
{
    Node* SubR = parent->_right;
    Node* SubRL = SubR->_left;
    int bf = SubRL->_bf;

    RotateR(SubR);
    RotateL(parent);
    
    if (bf == -1)
    {
        SubR->_bf = 1;
        SubRL->_bf = 0;
        parent->_bf = 0;
    }
    else if (bf == 1)
    {
        SubR->_bf = 0;
        SubRL->_bf = 0;
        parent->_bf = -1;
    }
    else if (bf == 0)
    {
        SubR->_bf = 0;
        SubRL->_bf = 0;
        parent->_bf = 0;
    }
    else
    {
        assert(false);
    }
}
AVL树插入代码:
bool Insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }
    cur->_parent = parent;

    //更新平衡因子
    while (parent)
    {
        if (cur == parent->_left)
        {
            parent->_bf--;
        }
        else
        {
            parent->_bf++;
        }

        if (parent->_bf == 0) // 1 -1 -> 0
        {
            //更新结束
            break;
        }
        else if (parent->_bf == 1 || parent->_bf == -1)  // 0 -> 1 -1
        {
            // 继续往上更新
            cur = parent;
            parent = parent->_parent;
        }
        else if (parent->_bf == 2 || parent->_bf == -2) // 1 -1 -> 2 -2
        {
            // 当前子树出问题了,需要旋转平衡一下
            if (parent->_bf == -2 && cur->_bf == -1)
            {
                RotateR(parent);
            }
            else if (parent->_bf == 2 && cur->_bf == 1)
            {
                RotateL(parent);
            }
            else if (parent->_bf == 2 && cur->_bf == -1)
            {
                RotateRL(parent);
            }
            else if (parent->_bf == -2 && cur->_bf == 1)
            {
                RotateLR(parent);
            }

            break;
        }
        else
        {
            // 理论而言不可能出现这个情况
            assert(false);
        }
    }
    return true;
}

五、AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

1. 验证其为二叉搜索树

如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

2. 验证其为平衡树

  • 每个节点子树高度差的绝对值不超过1
  • 节点的平衡因子是否计算正确

bool _Is_Balance(Node* root)
{
    if (root == nullptr)
    {
        return true;
    }
    int LeftHeight = _Height(root->_left);
    int RightHeight = _Height(root->_right);

    if (abs(LeftHeight - RightHeight >1))
    {
        cout<<"1:" << root->_kv.first << endl;
        return false;
    }

    if (RightHeight - LeftHeight != root->_bf)
    {
        cout <<"2:" << root->_kv.first << endl;
        cout << root->_parent->_kv.first << endl;
        cout << root->_bf << endl;
        return false;
    }
    return _Is_Balance(root->_left) && _Is_Balance(root->_right);
}

六、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即$log_2 (N)$。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

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

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

相关文章

YOLOv5独家改进:backbone改进 | 微软新作StarNet:超强轻量级Backbone | CVPR 2024

💡💡💡创新点:star operation(元素乘法)在无需加宽网络下,将输入映射到高维非线性特征空间的能力,这就是StarNet的核心创新,在紧凑的网络结构和较低的能耗下展示了令人印象深刻的性能和低延迟 💡💡💡如何跟YOLOv5结合:替代YOLOv5的backbone 收录 YOL…

一表捋清网络安全等级保护测评要求

三级网络安全等级保护测评指标&#xff1a; 对于中小企事业单位来说&#xff0c;网络安全建设是一个复杂且投入较高的过程&#xff0c;因此他们更倾向于寻找一种“省心省力”的等保建设方案&#xff0c;以及一种能够持续有效且具有较高性价比的网络安全建设投入方式。 此时&…

直流无刷电机控制(一)六步换相(有感霍尔)附六步换相实现代码

直流无刷电机概述 直流无刷电机的转子为永磁铁&#xff0c;定子为换相线圈&#xff0c;有别于有刷电机通过电刷或者换向器换相&#xff0c;无刷电机通过控制器电子换相。 极对数 直流无刷电机采用永磁铁作为转子&#xff0c;一对NS磁极为一极对&#xff0c;为了使电机运转更…

【js】获取媒体流实现拍照功能,摄像头切换

<script setup>import {onMounted,reactive,ref} from vueconst videoConstraints reactive({width: 500,height: 300});let picArr reactive([])let videoNode ref(null)let show ref(true)let stream reactive({})onMounted(async () > {// 获取视频流&#xf…

语义分割——高分卫星土地覆盖数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

在浏览器执行js脚本的两种方式

fetch请求get 在浏览器执行http请求,可以使用fetch函数; fetch(“url”).then(response => response.text()) .then(data => console.log(JSON.parse(data)[‘status’])) .catch(error => console.error(error)) 直接返回json数据: fetch(“url”).then(response…

深入解析Linux逻辑卷管理器(LVM)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Linux &#xff1a;从菜鸟到飞鸟的逆袭》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、Linux的起源与发展 2、什么是逻辑卷管理器&…

一觉醒来 AI科技圈发生的大小事儿 05月14日

&#x1f4f3;人类成功实现「蓝牙上天」&#xff01;接收来自600公里外太空信号 一家名为Hubble Network的初创公司成功实现了从地球到太空的蓝牙连接&#xff0c;通过SpaceX将两颗卫星送入轨道&#xff0c;从600公里外的卫星接收信号。他们开发了软件和相控阵天线&#xff0c…

设置linux终端用户输入空闲一段时间后就自动断开(linux终端超时自动断开)

在 /etc/profile 中加入TMOUT变量即可。 在文件的最后追加以下两行 export TMOUT600 # 600秒内无操作就断开。 readonly TMOUT # 将变量设置为只读&#xff0c;防止用户更改如图

好书推荐《智能网联汽车》

一本书打通 SLAM 在智能汽车 / 自动驾驶领域应用 自动驾驶技术已成为当今数字化时代汽车行业的热点话题之一。随着技术的不断成熟&#xff0c;越来越多的车辆采用激光 SLAM&#xff08;即时定位与地图构建&#xff09;和视觉 SLAM 技术&#xff0c;实现更高层次的智能网联汽车…

小果网页---套利系统添加了可以套利模块,提供数据api

最近在小果套利系统里面添加了一下可以套利模块&#xff0c;同时实现了盘中自动更新&#xff0c;30分钟更新一次。给大家提交交易参考&#xff0c;可以套利模块的选择 dfdf[df[申购状态] !暂停申购]dfdf[df[申购限额] !无限额]df[溢价率]df[溢价率].astype(float)df[成交量]df…

“网络安全新纪元:等保2.0的详细解读与实践”

网络安全等级保护基本要求》&#xff08;等保2.0&#xff09;于2019年6月发布&#xff0c;是我国网络安全等级保护制度的一项重要标准。等保2.0主要针对关键信息基础设施的网络安全保护&#xff0c;对数据安全和个人信息保护提出了更高的要求。本文将对等保2.0进行详细解读&…

leetcode-字符串变形-104

题目要求 思路 1.首先根据ASCII的规则&#xff0c;把字符串大小写替换&#xff0c;空格保持不变 2.将整个字符串进行翻转 3.以空格为区间&#xff0c;将区间内的字符串进行翻转&#xff0c;其中翻转的函数reverse() 代码实现 class Solution { public:string trans(string s…

数组定义方法

数组定义方法 "abcdef" 一个字符串 "a" "b" "c" "d" "e" "f" 字符串列表 ("a" "b" "c" "d" "e" &…

洛谷 P3372:线段树 1 ← 分块算法模板(区间更新、区间查询)

【题目来源】https://www.luogu.com.cn/problem/P3372【题目描述】 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; &#xff08;1&#xff09;将某区间每一个数加上 k。 &#xff08;2&#xff09;求出某区间每一个数的和。【输入格式】 第一行…

百面算法工程师 | YOLOv6面试考点原理全解析

本文给大家带来的百面算法工程师是深度学习目标检测YOLOv6面试总结&#xff0c;文章内总结了常见的提问问题&#xff0c;旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中&#xff0c;我们还将介绍一些常见的深度学习目标检测面试问题&#xff0c;并提供参考的回答…

pikachu靶场通关之暴力破解

目录 基于表单的暴力破解 1.打开网站&#xff0c;随便输入一个账号密码&#xff0c;点击登录 2.输入正确的账号密码&#xff0c;点击右上角的提示 3.随便输入账号密码&#xff0c;抓包 4.右键发送到intruder,点击intruder 5.设置攻击位置 6.设置攻击模式&#xff0c;选择…

【十大排序算法】----C语言版插入排序(详细图解)

目录 一&#xff1a;插入排序——原理 二&#xff1a;插入排序——分析 三&#xff1a;插入排序——实现 四&#xff1a;插入排序——效率 一&#xff1a;插入排序——原理 插入排序的原理和基本思想&#xff1a;把待排序的记录按其关键码值的大小逐个插入到一个已经排好序…

Python-VBA函数之旅-zip函数

目录 一、zip函数的常见应用场景 二、zip函数使用注意事项 三、如何用好zip函数&#xff1f; 1、zip函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;https://myelsa1024.blog.csdn.net/ 一、zip函数的常见…

【C语言每日题解】三题:回文检查、刘备 关羽 张飞三人过年放鞭炮、约瑟夫环问题(犹太人死亡游戏)(难度up,推荐)

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f308;感谢大家的阅读、点赞、收藏和关注 &#x1f970;希望大家喜欢我本次的讲解 &#x1f31f;非常推荐最后一道题 &#x1f339; 犹太人死亡游戏&#xff0c;建议观看 &…