[C++]AVL树怎么转

news2024/11/15 10:59:11

AVL树是啥

一提到AVL树,脑子里不是旋了,就是悬了。

AVL树之所以难,并不是因为结构难以理解,而是因为他的旋转。

AVL树定义

  • 平衡因子:对于一颗二叉树,某节点的左右子树高度之差,就是该节点的平衡因子
  • AVL树:对于一颗二叉树,任意节点的平衡因子bf 在范围[-1,1]之间(即左右子树高度差的绝对值<=1),则该树就是平衡二叉树

为何会有AVL树

AVL树是二叉搜索树的衍生,其名字来源是根据两位俄罗斯的数学家G.M.Adelson-VelskiiE.M.Landis,他们在1962年发明的一种用来解决二叉搜索树在极端情况下时间复杂度变为O(n)的情况。而其解决该情况的方法便是:通过旋转旋转来调整二叉搜索树的平衡

AVL树的节点

AVL树的节点实现方式有很多,这里采取下面的方法定义节点:

template<class T>
 struct AVLTreeNode
 {
     AVLTreeNode(const T& data)
         : _left(nullptr)
             ,_right(nullptr)
             ,_parent(nullptr)
             ,_data(data)
             ,_bf(0)
         {}

     AVLTreeNode<T>* _left;  // 该节点的左孩子
     AVLTreeNode<T>* _right; // 该节点的右孩子
     AVLTreeNode<T>* _parent;// 该节点的父亲
     T _data;//存储数据
     int _bf;//平衡因子  
};

这里AVL树节点的实现增加了_bf (平衡因子)成员变量,用来记录每个点的平衡因子。同时用了父节点的指针_parent ,目的是方便调整平衡因子。

AVL树的插入

这里AVL树的插入操作与二叉搜索树一样,唯一不同的是,在插入后要调整平衡因子

  • 对于插入的节点,因为其是插入到叶节点位置,所以他的平衡因子为0
  • 将节点插入后,树的高度可能会发生改变,此时则要调整他父亲,甚至还要调整父亲的父亲的平衡因子。主要分为以下几种情况

情况1:

在这里插入图片描述

如果在节点的右孩子插入,则该节点的bf需要+1(节点70、50的bf连锁着发生了变化,后面有说明)


情况2:

在这里插入图片描述

如果在节点的左孩子插入,则该节点的bf需要-1(节点70、50的bf连锁着发生了变化,后面有说明)

但是没完!

如果节点的平衡因子因为插入而变成了1 或者-1 ,则说明子树的高度发生了变化,此时该节点的父节点bf也应发生变化(如果父节点的bf更改之后影响了“爷爷节点”,则爷爷节点也要跟着跟着变化)。所以上图中的70和50两节点的bf也要发生变化。


但是还没完!

bf 的值有可能会变为2、-2,则此时就需要进行旋转操作(旋转完成后,会手动调整bf,保证其符合要求)

代码:

//插入操作
...
//调整平衡因子
cur = newnode;
parent = newnode->_parent;
while (parent)
{
    if (parent->_right == cur)
    {
        ++parent->_bf;
    }
    else if (parent->_left == cur)
    {
        --parent->_bf;
    }
    if (parent->_bf == 0)//AVL树稳定了,break出去
        break;
    else if (parent->_bf == 1 || parent->_bf == -1)//无需旋转但是需要更改父亲的平衡因子
    {
        cur = parent;
        parent = parent->_parent;
    }
    else if ()//需要旋转
    {
        
    }
}

AVL树的旋转

重中之重,也是难中之难

AVL树旋转的目的

AVL树是一个平衡二叉搜索树,因为某些插入操作导致它不再平衡(具体表现是平衡因子变为2或-2),则此时为了使之平衡,就需要进行旋转操作

AVL树旋转操作

AVL树的旋转主要分为4种情况:

  1. 左旋
  2. 右旋
  3. 左旋再右旋(双旋)
  4. 右旋再左旋(双旋)

情况1:左旋

在这里插入图片描述

对于插入后父亲的bf=2,右孩子的bf=1的情况,采用左旋。且左旋后平衡因子都是0

在这里插入图片描述


情况2:右旋

在这里插入图片描述

对于插入后父亲的bf=-2,左孩子的bf=-1的情况,采用右旋。且右旋后平衡因子都是0

在这里插入图片描述


双旋

这种情况的发生是因为发现对该节点无论左旋还是右旋,都无法使其平衡,原因是插入节点的位置比较特殊

情况3:左旋再右旋

在这里插入图片描述

对于插入(这里插入b还是c只会影响旋转完后的平衡因子)后父亲的bf=-2,左孩子的bf=1的情况,采用左旋再右旋。这里的平衡因子更新规则与前不同,详见后文。

在这里插入图片描述


情况4:先右旋再左旋

在这里插入图片描述

对于插入(这里插入b还是c只会影响旋转完后的平衡因子)后父亲的bf=2,右孩子的bf=-1的情况,采用右旋再左旋。这里的平衡因子更新规则与前不同,详见后文。
在这里插入图片描述

双旋的平衡因子更新规则

更新规则相比旋转规则就简单许多

分为以下3种情况(以上图为例):

  1. 如果60的平衡因子(旋转前)是-1,则更新后的60的平衡因子变为0,左孩子变为0,右孩子变为1
  2. 如果60的平衡因子(旋转前)是1,则更新后60的平衡因子变为0,左孩子变为-1,右孩子变为0
  3. 如果60的平衡因子(旋转前)是0,则更新后60的平衡因子变为0,左孩子变为0,右孩子变为0

代码:

这里只给出一部分代码,剩下的可以自己尝试写出来,然后可以到仓库对照

bool insert(const pair<K,V>& kv)
{
    //插入
    Node* newnode = new Node(kv);
    if (newnode == nullptr) return false;
    if (_root == nullptr)
    {
        _root = newnode;
        return true;
    }
    Node* cur = _root;
    Node* parent = nullptr;
    while (cur)
    {
        parent = cur;
        if (kv.first > cur->_kv.first)
        {
            cur = cur->_right;
        }
        else cur = cur->_left;
    }
    //调整节点连接
    if (kv.first > parent->_kv.first)
        parent->_right = newnode;
    else 
        parent->_left = newnode;
    newnode->_parent = parent;
    //调整平衡因子
    cur = newnode;
    parent = newnode->_parent;
    while (parent)
    {
        if (parent->_right == cur)
        {
            ++parent->_bf;
        }
        else if (parent->_left == cur)
        {
            --parent->_bf;
        }
        if (parent->_bf == 0)//AVL树稳定了,break出去
            break;
        else if (parent->_bf == 1 || parent->_bf == -1)//无需旋转但是需要更改父亲的平衡因子
        {
            cur = parent;
            parent = parent->_parent;
        }
        else if (parent->_bf == 2 || parent->_bf == -2)//需要旋转
        {
            if (parent->_bf == 2 && cur->_bf == 1)
            {
                RotateL(parent);
            }
            else if (parent->_bf == -2 && cur->_bf == -1)
            {
                RotateR(parent);
            }
            else if (parent->_bf == 2 && cur->_bf == -1)
            {
                RotateRL(parent);
            }
            else if (parent->_bf == -2 && cur->_bf == 1)
            {
                RotateLR(parent);

            }
            else
            {
                assert(false);
            }
        }
    }
    return true;
}
//左旋
void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    Node* pparent = parent->_parent;

    parent->_parent = subR;
    subR->_left = parent;

    parent->_right = subRL;
    if (subRL)
    {
        subRL->_parent = parent;
    }

    if (!pparent)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (pparent->_left == parent)
        {
            pparent->_left = subR;
        }
        else
        {
            pparent->_right = subR;
        }
        subR->_parent = pparent;
    }
    parent->_bf = 0;
    subR->_bf = 0;
}
//先左旋,再右旋
void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;
    RotateL(parent->_left);
    RotateR(parent);
    if (bf == 0)
    {
        subLR->_bf = 0;
        parent->_bf = 0;
        subL->_bf = 0;
    }
    else if (bf == 1)
    {
        subLR->_bf = 0;
        parent->_bf = 0;
        subL->_bf = -1;
    }
    else if (bf == -1)
    {
        subLR->_bf = 0;
        parent->_bf = 1;
        subL->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

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

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

相关文章

训练2布线 玛露希尔

前言 这不是雕刻 是布线纹理绘制blender材质渲染 2D 3D 起手式 八点眼 侧峰线 三角鼻(三面鼻) 下颚角 额, 嘴 线 法令环 橄榄嘴

linux高级编程:线程(二)、进程间的通信方式

线程&#xff1a; 回顾线程&#xff08;一&#xff09;&#xff1a; 1.线程间通信问题 线程间共享同一个资源&#xff08;临界资源&#xff09; 互斥&#xff1a; 排他性访问 linux系统 -- 提供了Posix标准的函数库 -- 互斥量&#xff08;互斥锁&#xff09; 原子操作&#x…

内存管理 -----分段分页

分段 分段&#xff1a;程序的分段地址空间&#xff0c;分段寻址方案 两个问题 分段 &#xff1a;是更好分离和共享 左边是有序的逻辑地址&#xff0c;右边是无序的物理地址&#xff0c;然后需要有一种映射的关系&#xff08;段关联机制&#xff09; 各个程序的分配相应的地址…

Pygame教程01:初识pygame游戏模块

Pygame是一个用于创建基本的2D游戏和图形应用程序。它提供了一套丰富的工具&#xff0c;让开发者能够轻松地创建游戏和其他图形应用程序。Pygame 支持许多功能&#xff0c;包括图像和声音处理、事件处理、碰撞检测、字体渲染等。 Pygame 是在 SDL&#xff08;Simple DirectMed…

181基于matlab的利用LMS算法、格型LMS算法、RLS算法、LSL算法来估计线性预测模型参数a1和a2

基于matlab的利用LMS算法、格型LMS算法、RLS算法、LSL算法来估计线性预测模型参数a1和a2&#xff1b;预测信号由二阶线性预测模型产生。2.利用LMS算法和RLS算法将一个叠加有噪声的信号实现噪声消除&#xff0c;恢复原始信号。有22页试验分析文档。&#xff08;包括程序在内&…

通过MNIST手写数字识别任务快速入门深度学习(事无巨细版)

什么是深度学习 深度学习(DL, Deep Learning)是机器学习(ML, Machine Learning)领域中一个研究方向。 深度学习通过对样本数据的内在规律和特征的提取与抽象,在不同维度和层次上进行处理,让机器能够像人一样具有分析学习能力,能够识别文字、图像和声音等数据。 相比于初期的…

【算法科目】2024年第二届全国大学生信息技术认证挑战赛 题解

图像压缩 曾经看到过&#xff0c;这是一道洛谷原题&#xff0c;很可惜我没做过&#xff0c;有点看不懂就没尝试。 原题链接&#xff1a;B3851 [GESP202306 四级] 图像压缩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 因数分解 直接枚举就行了&#xff0c;从2开始找因子&a…

python中的类与对象(3)

目录 一. 类的多继承 二. 类的封装 三. 类的多态 四. 类与对象综合练习&#xff1a;校园管理系统 一. 类的多继承 在&#xff08;2&#xff09;第四节中我们介绍了什么是类的继承&#xff0c;在子类的括号里面写入要继承的父类名。上一节我们只在括号内写了一个父类名&…

Latex中如何调整算法伪代码\begin{algorithm}的字体大小?

在LaTeX中&#xff0c;要调整algorithm环境&#xff08;通常与algorithmic、algorithmicx、algorithm2e等包一起使用来编写伪代码&#xff09;中的字体大小&#xff0c;你可以使用\small、\footnotesize、\tiny等命令来减小字体大小&#xff0c;或者使用\large、\Large、\LARGE…

简单实现文字滚动效果-CSS版本

先看看效果 话不多说直接上代码 <template><div class"main"><div class"scroll-region"><div class"swiper-scroll-content"><span class"list-btn" v-for"(item, index) in overviewList" :…

【重温设计模式】命令模式及其Java示例

命令模式的介绍 在我们的编程世界中&#xff0c;设计模式如同一盏指路明灯&#xff0c;它们是前人智慧的结晶&#xff0c;为我们照亮了解决复杂问题的路径。 其中&#xff0c;命令模式是一种行为设计模式&#xff0c;其主要的目标是将操作封装到对象中。这种模式的一个显著特点…

基于springboot+vue的图书电子商务网站

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

数据修改

Oracle 目录 数据修改 将员工编号的 7369 的员工工资修改为 810&#xff0c;佣金改为 100 将工资最低的员工工资修改为公司的平均工资 将所有在 1981 年雇佣的员工的雇佣日期修改为今天&#xff0c;工资增长 20% 数据的更新操作 Oracle从入门到总裁:https://blog.csdn.n…

学习人工智能的方法及方向!

目录 一、第一部分&#xff1a;了解人工智能 二、人工智能学习路线图 三、职业规划 四、未来展望 五、总结 在这个信息爆炸的时代&#xff0c;想要系统性地学习人工智能&#xff08;AI&#xff09;并找到对应方向的工作&#xff0c;你需要一个明确的学习路径和职业规划。本…

【latex】\IEEEpubid版权声明与正文内容重叠

问题描述 撰写IEEE Trans论文时&#xff0c;出现版权声明文字\IEEEpubid与正文内容重叠的问题&#xff1a; 原因分析&#xff1a; 在使用模板时&#xff0c;不小心将以下命令删除了&#xff1a; \IEEEpubidadjcol 解决方案&#xff1a; 在需要换页的位置附近添加以上命令&…

投影和定义投影的区别

Arcmap中关于投影的工具有四个&#xff0c;分别是定义投影、投影、投影栅格、批量投影。这四个工具既有相同之处也有不同之处&#xff0c;下面我将一一介绍。 ①定义投影&#xff1a;Arcmap中关于定义投影工具是这样描述的&#xff1a;“所有地理数据集均具有一个用于显示、测…

【LeetCode题解】2859. 计算 K 置位下标对应元素的和+938. 二叉搜索树的范围和+1028. 从先序遍历还原二叉树(三种方法:栈+递归+集合)

文章目录 [2859. 计算 K 置位下标对应元素的和](https://leetcode.cn/problems/sum-of-values-at-indices-with-k-set-bits/)思路&#xff1a; [938. 二叉搜索树的范围和](https://leetcode.cn/problems/range-sum-of-bst/)思路&#xff1a;写法一&#xff1a;在中间累加写法二…

从零开始学习Netty - 学习笔记 -Netty入门【半包,黏包】

Netty进阶 1.黏包半包 1.1.黏包 服务端代码 public class HelloWorldServer {private static final Logger logger LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());public static void main(String[] args) {NioEventLoopGroup bossGroup new NioEventL…

Linux多线程控制:深入理解与应用(万字详解!)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;どうして (feat. 野田愛実) 0:44━━━━━━️&#x1f49f;──────── 3:01 &#x1f504; ◀️ ⏸ ▶️ …

基于redis实现【最热搜索】和【最近搜索】功能

目录 一、前言二、分析问题三、针对两个问题&#xff0c;使用redis怎么解决问题&#xff1f;1、字符串String2、列表List3、字典Hash4、集合Set5、有序集合ZSet6、需要解决的五大问题 四、编写代码1.pom依赖2.application.yml配置3.Product商品实体4.用户最近搜索信息5.redis辅…