新C++(9):谈谈,翻转那些事儿

news2025/7/13 8:30:53

"相信羁绊,相信微光,相信一切无常。"

一、AVL树翻转那些事儿

(1)什么是AVL树?

在计算机科学中, AVL树是最先发明的自平衡二叉查找树。 在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为 高度平衡树。而保证这颗树平衡的关键步骤,就是通过旋转来保证,由于增加、删除时对树的破坏的平衡。 取自这里

这样的树形结构,能够十分高效地查找"键值"(key/value模型)。C++中的STL容器,如map、set其底层就是由红黑树实现的。当然这是后半段会讲的一种优秀的数据结构。

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

形如上图,每个节点的上标是该节点的左右子树的高度差。如果该AVL二叉树有N个节点,那么其高度可以保持LogN(以2为底),其搜索查找的时间复杂度为LogN(以2为底)。

(2)翻转那些事儿

条件检测;

上面的简介也说过,AVL树的平衡是通过旋转来维护的。那么什么时候需要翻转呢?什么时候不需要呢?我们在这里对每个节点引入 "平衡因子(bf)"的概念。对节点左右子树高度的检测,转移到了对平衡因子数值状态的检测。

root(当前节点) 我们对插入节点对平衡因子的更改做一个约定:左插入-- 右插入++。
① bf == 0时,说明当前左右子树高度平衡,不需要调整。
② bf == 1 || bf == -1时,说明这颗"root一定是从bf == 0变化而来的"。那么就不仅仅需要关注当前root节点平衡因子的变化,还需要"向上调整"。root = root->parent
③ bf == 2 || bf == -2 时,此时AVL条件树的已经不平衡了 需要手动翻转才能保证其平衡性。

单线旋转;

节点属性:

template<class K,class V>
struct AVLTreeNode
{
    std::pair<K, V> _kv;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    int _bf = 0;

    AVLTreeNode(const std::pair<K,V> kv)
        :_kv(kv),
        _left(nullptr),
        _right(nullptr),
        _bf(0)
    {}
};

RotateR:

①让subLR作为左子树 连接在parent的左边。
②parent再作为subL的右子树连接。
③subL成为新的当前左右子树的根,并与上层parent->parent连接
void RotateL(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    parent->_left = subLR;
    if (subLR)
    {
        //subLR 如果不为空才需要 连接parent
        subLR->_parent = parent;
    }
    
    //连接诶父节点
    Node* ppNode = parent->_parent;
    subL->_right = parent;
    parent->_parent = subL;

    if (root == nullptr)
    {
        subL = root;
        subL->_parent == nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }
        subL->_parent = ppNode;
    }
    //更新平衡因子
    subL->_bf = parent->_bf = 0;
}

RotateL:

①让subRL作为30的右子树连接。
②parent作为subR的左子树连接。
③subR作为新的当前左右子树的根,并与上层parent->parent连接。
void RotateR(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

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

    Node* ppNode = parent->_parent;
    parent->_parent = subR;
    subR->_right = parent;

    if (root== nullptr)
    {
        root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR;
        }
        else
        {
            ppNode->_right = subR;
        }
        subR->_parent = ppNode;
    }

    subR->_bf = parent->_bf = 0;
}

折线旋转;

其实单线旋转蛮简单的,并且旋转过后平衡因子都可以被直接处理为0。但是,如果是面对折线旋转的情况,仅仅考单旋处理,是不够的。

左右双旋:

右左双旋:

双旋的问题,我觉得难点不是在于旋转,因为前面已经解决了。而是如何处理双旋后的平衡因子与什么时候用双旋,什么时候用单旋?

单旋的判断很简单,因为是一条直线
即: (parent->bf == 2 && cur->bf == 1) || (parent->bf == -2 && cur->bf == -1)

双旋使用的场景,就是针对折线的插入节点
即: (parent->bf == 2 && cur->bf == -1) || (parent->bf == -2 && cur->bf == 1)
单旋情况下平衡因子的处理颇为简单易懂,一次翻转就可以将左右子树高度调平。

双旋情况下的平衡因子取决于 subRL \ subLR 到底是左子树+1 还是右子树+1
对于右左双旋的情况,subRL的左子树会被parent去接手,反之subRL的右子树会被subL接手。

对于左右双旋的情况,subLR的左子树会被subR接手,而它的右子树会被parent接手。
其实还是一句话,画图才是王道
void RotateRL(Node* parent)
{
    Node* subL = parent->_right;
    Node* subLR = subL->_left;
    //依据
    int bf = subLR->_bf;
    RotateR(subL);
    RotateL(parent);
    
    //说明是在cur的 左边插入
    if (bf == -1)
    {
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 1;
    }
    else if (bf == 1)
    {
        //说明是在cur的 右边插入
        subL->_bf = -1;
        subLR->_bf = 0;
        parent->_bf = 0;
    }
    else
    {
        subL->_bf = 0;
        subLR->_bf = 0;
        parent->_bf = 0;
    }
}

void RotateLR(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = parent->_left;
    int bf = subRL->_bf;
    RotateL(subR);
    RotateR(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
    {
        subR->_bf = 0;
        subRL->_bf = 0;
        parent->_bf = 0;
    }
}


二、红黑树翻转那些事儿

(1)什么是红黑树?

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。

为什么有了AVL树,又来了个红黑树呢?那必定红黑树一定有比起AVL树更加优势的地方。可以说,"AVL树是一位大佬设想,那么红黑树一定是出自一位天才的手笔。"

红黑树规则:
①每个结点不是红色就是黑色。
②根节点一定是黑色。
③不能出现连续的红色结点。
④对于每个结点到后代结点的简单路径上,都含有相同的黑色结点。

红黑树没有像AVL树那么严格地控制平衡(左右子树高度差不超过1),它是一种接近平衡的搜索二叉树。

红黑树规则的核心: 确保最长路径的结点数不超过最短路径节点数的2倍

(2)翻转那些事儿

条件检测:

红黑树插入结点的情况,其实可以分为两大类:

①cur(新增结点)红 parent红 grandparent黑 uncle红

②cur(新增结点)红 paren红 grandparent黑 uncle不存在或者存在且为黑。

由此红黑树插入的关键为:uncle结点。

下面来看看这两种情况的对应图:

因为牵涉到翻转,正好我们也在AVL树那一小结写过,直接CV一份~

void RotateL(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;

    parent->_left = subLR;
    if (subLR)
    {
        subLR->_parent = parent;
    }

    Node* ppNode = parent->_parent;
    subL->_right = parent;
    parent->_parent = subL;

    if (ppNode->_parent == nullptr)
    {
        subL = root;
        subL->_parent == nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subL;
        }
        else
        {
            ppNode->_right = subL;
        }
        subL->_parent = ppNode;
    }
}


void RotateR(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;

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

    Node* ppNode = parent->_parent;
    parent->_parent = subR;
    subR->_right = parent;

    if (root== nullptr)
    {
        root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR;
        }
        else
        {
            ppNode->_right = subR;
        }
        subR->_parent = ppNode;
    }

}

红黑树结点结构;

enum Color
{
    RED,
    BLACK
};

template<class K, class V>
struct RBTreeNode
{
    std::pair<K, V> _kv;
    RBTreeNode<K, V>* _left = nullptr;
    RBTreeNode<K, V>* _right = nullptr;
    RBTreeNode<K, V>* _parent = nullptr;
    Color _color = RED;

    RBTreeNode(const std::pair<K, V>& kv)
        :_kv(kv)
    {}
};

uncle存在且为红:

我们让 parent 与 uncle同时变黑 并且 grandparent变为红色。

就结束了吗?当然不是!

bool Insert(const std::pair<K,V>& kv)
{
    //..
    Node* cur = new Node(kv);
    Node* parent = cur->_parent;

    while (parent &&parent->_color!= RED)
    {
        Node* grandfather = parent->_parent;
        //找到uncle节点
        Node* uncle = nullptr;
        if (grandfather->_left == parent)
        {
            uncle = grandfather->_right;
            //1.uncle存在且为红
            if (uncle && uncle->_color == RED)
            {
                //着色
                uncle->_color = parent->_color = BLACK;
                grandfather->_color = RED;
                //继续向上调整
                cur = grandfather;
                parent = cur->_parent;
            }
             //......
        }
        else
        {
            uncle = grandfather->_left;
            //1.uncle存在且为红
            if (uncle && uncle->_color == RED)
            {
                //着色
                uncle->_color = parent->_color = BLACK;
                grandfather->_color = RED;
                //继续调整
                cur = grandfather;
                parent = cur->_parent;
            }
            //......
        }
    }

}

uncle不存在或者uncle存在且为黑(cur为直线):

我们让 parent变黑 grandfather变红
//uncle = grandfather->_right;
//uncle不存在或者存在且为黑
//parent在左  uncle在右  左子树
if (cur == parent->_left)
{
    RotateR(grandfather);
    //着色
    grandfather->_color = RED;
    parent->_color = BLACK;
}

//uncle = grandfather->_left;
//uncle不存在或者存在且为黑
//parent在右  uncle在左 右子树
if (cur == parent->_right)
{
    RotateL(grandfather);
    grandfather->_color = RED;
    parent->_color = BLACK;
}

uncle不存在或者uncle存在且为黑色(折线)

进一步复杂的情况,不单单是用单旋搞定。

我们将cur变黑,grandfather变红
//uncle = grandfather->_right;
else{
    RotateL(parent);
    RotateR(grandfather);
    grandfather->_color = RED;
    cur->_color = BLACK;
}
//uncle = grandfather->_left;
//cur == parent->_left
else{
    RotateR(parent);
    RotateL(grandfather);
    grandfather->_color = RED;
    cur->_color = BLACK;
}

总结:

AVL与红黑树都是搜索效率极其强悍的数据结构。红黑树不追求绝对的平衡,但是AVL却对左右子树的平衡关系严格要求。因此,对树的翻转次数一定多余红黑树。在插入时其性能效率也会相应受到影响。而且红黑树实现比较简单,所以实际运用中红黑树更多。

本篇到此为止,感谢你的阅读。

祝你好运,向阳而生~

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

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

相关文章

网上插画教学哪家质量好,汇总5大插画培训班

网上插画教学哪家质量好&#xff1f;给大家梳理了国内5家专业的插画师培训班&#xff0c;最新五大插画班排行榜&#xff0c;各有优势和特色&#xff01; 一&#xff1a;国内知名插画培训机构排名 1、轻微课&#xff08;五颗星&#xff09; 主打课程有日系插画、游戏原画、古风插…

Tencent OS下逻辑卷(LVM)创建和扩容

测试环境是一个虚拟机&#xff0c;原配置1个虚拟盘。 创建4个虚拟盘&#xff0c;每盘2G并挂载在虚拟主机上&#xff0c;启动虚拟主机开始测试。 LVM英文是Logical Volume Manager&#xff0c;直接翻译为逻辑卷管理。 这种磁盘管理模式比较灵活&#xff0c;在磁盘空间不足的时…

深入浅出C++ ——容器适配器

文章目录一、容器适配器二、deque类简介1. deque的原理2. deque迭代器3. deque的优点和缺陷4. 为什么选择deque作为stack和queue的底层默认容器一、容器适配器 适配器的概念 适配器是STL六大核心组件之一&#xff0c;它是一种设计模式&#xff0c;该种模式是将一个类的接口转换…

大规模 IoT 边缘容器集群管理的几种架构-2-HashiCorp 解决方案 Nomad

前文回顾 大规模 IoT 边缘容器集群管理的几种架构-0-边缘容器及架构简介大规模 IoT 边缘容器集群管理的几种架构-1-RancherK3s &#x1f4da;️Reference: IoT 边缘计算系列文章 HashiCorp 解决方案 - Nomad Docker 简介 Nomad: 一个简单而灵活的调度器和编排器&#xff0c;…

网络工程课(二)

ensp配置vlan 一、配置计算机ip地址和子网掩码 二、配置交换机LSW1 system-view [Huawei]sysname SW1 [SW1]vlan batch 10 20 [SW1]interface Ethernet0/0/1 [SW1-Ethernet0/0/1]port link-type access 将接口设为access接口 [SW1-Ethernet0/0/1]port default vlan 10 [SW1-E…

【MyBatis】源码学习 04 - 从 MapperMethod 简单分析一条 SQL 的映射操作流程

文章目录前言参考目录学习笔记1、测试代码说明2、binding 包的主要功能3、获取 Mapper 接口实例过程4、SQL 语句执行流程4.1、方法调用器4.2、MapperMethod 绑定方法4.2.1、SqlCommand4.2.2、MethodSignature4.3、MapperMethod#execute前言 本文内容对应的是书本第 13 章的内容…

【亲测2022年】网络工程师被问最多的面试笔试题

嗨罗~大家好久不见&#xff0c;主要是薄荷呢主业还是比较繁忙的啦&#xff0c;之前发了一个面试题大家都很喜欢&#xff0c;非常感谢各位大佬对薄荷的喜爱&#xff0c;嘻嘻然后呢~薄荷调研了身边的朋友和同事&#xff0c;发现我们之前去面试&#xff0c;写的面试题有很多共同的…

C++ Effictive 第6章 继承与面向对象设计 笔记

继承意味着"is-a"。如果B继承自A&#xff0c;那么B is-a A。 子类声明与父类函数同名的函数时&#xff0c;父类函数会被遮掩。 使用using Base::func(args...)&#xff1b;父类所有func的重载函数都在子类中被声明。此举下&#xff0c;如果子类函数与父类函数参数也一…

不要对chatgpt过度反思 第一部分

最近一段时间&#xff0c;chatgpt很热&#xff0c;随意翻一些文章或视频&#xff0c;一些非常整齐一致的怪论&#xff0c;时不时都会冒出来。 为什么这种革命性创新又出现美国&#xff1f; 为什么我国互联网只会电商&#xff0c;没有创新&#xff1f; 为什么我们做不出来&…

列表推导式_Python教程

内容摘要 Python中存在一种特殊的表达式&#xff0c;名为推导式&#xff0c;它的作用是将一种数据结构作为输入&#xff0c;再经过过滤计算等处理&#xff0c;最后输出另一种数据结构。根据数据结构的不同会被分为列表推导式、 文章正文 Python中存在一种特殊的表达式&#x…

股票、指数、快照、逐笔... 不同行情数据源的实时关联分析应用

在进行数据分析时经常需要对多个不同的数据源进行关联操作&#xff0c;因此在各类数据库的 SQL 语言中均包含了丰富的 join 语句&#xff0c;以支持批计算中的多种关联操作。 DolphinDB 不仅通过 join 语法支持了对于全量历史数据的关联处理&#xff0c;而且在要求低延时的实时…

Qt信号与槽使用方法总结

前言 在图形界面编程中QT是为首选&#xff0c;组件之间如何实现通信是核心的技术内容。Qt 使用了信号与槽的机制&#xff0c;非常的高效、简单、易学&#xff0c;方便开发者的使用。本文详细的介绍了Qt 当中信号与槽的概念&#xff0c;并演示了各种信号与槽的连接方式。 什么…

你知道 GO 中的 协程可以无止境的开吗?

GO语言天生高并发的语言&#xff0c;那么是不是使用 go 开辟协程越多越好的&#xff0c;那么在 go 里面&#xff0c;协程是不是可以开无限多个呢&#xff1f; 那么我们就一起来看看尝试写写 demo 吧 尝试开辟尽可能多的 协程 写一个 demo &#xff0c;循环开 1 << 31 …

自由变化,功能增强,适配优化—V6.0.2版本发布

本次更新&#xff1a;经过两个月的细节打磨&#xff0c; V6.0.2版本发布&#xff0c;自由变化&#xff0c;功能增强&#xff0c;适配优化&#xff1b;新版本增加了超级弹窗&#xff0c;可以多窗口并存&#xff1b;增加了编号组件&#xff0c;可以调用编号组件库&#xff0c;自动…

Artiifact分析HSV数据

Artiifact分析HSV数据1 下载Artiifact分析工具2 安装软件后打开软件3 分析1 Extract IBIS from ECG data2 Detect and process artifact in IBI data3 Analse HRVARTiiFACT&#xff0c;这是一种用于处理心电图和 IBI 数据的软件工具。图形用户界面中提供了自动和手动伪影检测和…

JavaEE|TCP/IP协议栈之TCP协议端格式详解

文章目录一、对TCP协议的感性认识简介特点二、TCP的报文结构概览16位端口号和16位目的端口号32位序号和32位确认序号4位首部长度保留位&#xff08;6位&#xff09;6个标志位16位窗口大小16位校验和16位紧急指针40位头部选项&#xff08;option&#xff09;参考一、对TCP协议的…

【新品发布】三颗国产新“芯”硬核亮相——1颗电机驱动IC+2颗电源管理IC

拓尔“芯”常态&#xff0c;性能为王创新进取永不止步&#xff01;自2023年新品发布会1月场发布5款重磅新品后&#xff0c;伴着春律&#xff0c;2月场它来了&#xff01; 2023年2月20日晚&#xff0c;拓尔微线上发布电机驱动芯片TMI8723及两颗电源管理芯片TMI7205B、TMI5122D8…

初识SSTI

SSTI概念SSTI就是服务器端模板注入&#xff08;Server-Side Template Injection&#xff09;&#xff0c;实际上也是一种注入漏洞&#xff1b;可以类比于SQL注入&#xff0c;实际上这两者的基本思想是一致的&#xff1b;SSTI也是获取了一个输入&#xff0c;然后在后端的渲染处理…

AVS3中ECCSAO

AVS3引入了CCSAO&#xff0c;通过不同通道间的预测来去除通道间的冗余以提升编码效率。然而CCSAO在处理时未考虑边界像素的分类问题&#xff0c;所以在处理纹理复杂的内容时效率还是不高。CCSAOAVS3中引入了CCSAO&#xff0c;它是帧级的环路滤波工具&#xff0c;如Fig.1所示&am…

ABAP 351 - 动态编程

作为面对对象的编程语言&#xff0c;ABAP也是支持动态编程的。ABAP351作为一门独立的课程介绍了类反射机制如何实现的过程。一、Field SymbolsField Symbols(字段符号)在ABAP编程中经常使用&#xff0c;实际上它具备以下几点特性&#xff1a;字段符号只是字段的一个别名&#x…