<C++>红黑树

news2024/11/22 15:02:32

文章目录

  • 1. 红黑树的概念
  • 2. 红黑树的性质
  • 3. 红黑树节点定义
  • 4. 红黑树的插入操作
  • 5. 红黑树的验证
  • 6. 红黑树与AVL树的比较
  • 7. 红黑树模拟实现STL中的map与set

1. 红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

2. 红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

最短路径:全黑(如果没有全黑的,就是红节点最少的那条路径)

最长路径:一黑一红间隔

并且对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。可以得出:其最长路径中节点个数不会超过最短路径节点个数的两倍

3. 红黑树节点定义

enum Colour
{
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		: _kv(kv)
		, _parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _col(RED)// 选择更好搞定的
	{}
};

思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?

  • 新增节点是红色,可能破坏规则3【如果一个节点是红色的,则它的两个孩子结点是黑色的】
  • 新增节点是黑色,一定破坏规则4【对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点】,并且规则4很难维护(要牵扯到更多节点)

4. 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点

  2. 检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
1️⃣情况一: cur为红,p为红,g为黑,u存在且为红

将g改为黑色的原因:为了不破坏规则4【对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点】。这棵树有可能是局部子树,原本它的两条路径上都有1个黑结点,如今将u和p变黑,这两条路径上就分别多了一个黑结点,违反规则4,此时把g改为黑色能避免这种问题。

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
不关心左右关系(p、u是g的左还是右不影响;cur是p的左还是右也没关系),因为只变色,不旋转

image-20220903165123907

2️⃣情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

u有两种情况:

  • 如果u不存在。右只有一个黑,左也要只有一个黑,左不能有新的黑,左的高度不能大于2,所以cur是新增红结点。变色:新根变黑(对向上的数结点没影响了);g变红(维持黑的数量)
  • 如果u存在且为黑。cur一定不是新增,否则cur插入前该树不符合规则4。由规则3得:cur原本是黑的,是由情况一的处理方式变成红色的

image-20220903165137181

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红

3️⃣情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

情况三和情况二的区别:

p是g的左,cur是p的左,是情况二——单旋

p是g的左,cur是p的右,是情况三——双旋

image-20220903165251050

u有两种情况:

  • 如果u不存在。右只有一个黑,左也要只有一个黑,左不能有新的黑,左的高度不能大于2,所以cur是新增红结点。变色:新根变黑(对向上的数结点没影响了);g变红(维持黑的数量)
  • 如果u存在且为黑。cur一定不是新增,否则cur插入前该树不符合规则4。由规则3得:cur原本是黑的,是由情况一的处理方式变成红色的

就只是跟情况二的左右关系交换了一下

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;

相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转

则转换成了情况2

//按搜索二叉树规则插入
//更新平衡因子,旋转使其平衡
bool Insert(const pair<K, V>& kv)
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        _root->_col = BLACK;
        return true;
    }

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

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

    // 存在连续红色节点
    while (parent && parent->_col == RED)
    {
        Node* grandfather = parent->_parent;// parent是红的,一定存在grandparent
        if (parent == grandfather->_right)// 区分左右
        {
            Node* uncle = grandfather->_left;
            // 情况一
            if (uncle && uncle->_col == RED)// 叔叔存在且为红
            {
                // 变色
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;
                // 向上处理
                cur = grandfather;
                parent = cur->_parent;
            }
            else// 叔叔不存在 或者 叔叔存在且为黑
            {
                // 情况二:(直线)
                //   g           p
                //     p    -> g    c
                //       c
                if (cur == parent->_right)
                {
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                // 情况三:(折线)
                //   g        c
                //     p  -> g   p
                //    c
                else
                {
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }

                break;
            }
        }
        else
        {
            Node* uncle = grandfather->_right;
            // 情况一
            if (uncle && uncle->_col == RED)// 叔叔存在且为红
            {
                // 变色
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;
                // 向上处理
                cur = grandfather;
                parent = cur->_parent;
            }
            else// 叔叔不存在 或者 叔叔存在且为黑
            {
                // 情况二:(直线)
                //   g        p
                //  p  ->   c   g
                // c
                if (cur == parent->_left)
                {
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                }
                // 情况三:(折线)
                //   g        c
                // p	->	p   g
                //   c
                else
                {
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    _root->_col = BLACK;// 情况一改变根之后,一定要记得把根变为黑
    return true;
}

5. 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool _IsValidRBTree(Node* root, size_t k, const size_t blackCount)
{
    if (nullptr == root)
    {
        if (blackCount != k)
        {
            cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
            return false;
        }
        return true;
    }

    if (BLACK == root->_col)
        ++k;

    // 检查连续红节点:遇到红色节点检查父亲
    if (RED == root->_col && root->_parent && RED == root->_parent->_col)
    {
        cout << "违反性质三:存在连在一起的红色节点" << endl;
        return false;
    }

    return _IsValidRBTree(root->_left, k, blackCount)
        && _IsValidRBTree(root->_left, k, blackCount);
}

bool IsBalanceTree()
{
    // 检查规则
    Node* pRoot = _root;
    if (pRoot == nullptr)
        return true;

    if (pRoot->_col != BLACK)
    {
        cout << "根节点不是黑的" << endl;
        return false;
    }

    // 检查每个路径黑节点数量:求最左路径,比较基准值
    size_t blackCount = 0;
    Node* pCur = pRoot;
    while (pCur)
    {
        if (BLACK == pCur->_col)
            blackCount++;

        pCur = pCur->_left;
    }

    size_t k = 0;
    return _IsValidRBTree(pRoot, k, blackCount);
}

6. 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多

7. 红黑树模拟实现STL中的map与set

具体请看我的gitee吧

链接:https://gitee.com/symng/cpp/tree/master/map_set/map_set

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

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

相关文章

小程序管理还能这样做,让小程序管理更高效

说起小程序&#xff0c;作为开发者或者企业用户不得不面临一个问题就是&#xff0c;需要小程序承载的业务越来越多的时候&#xff0c;小程序的数量也呈现增长&#xff0c;随之而来的就是小程序开发、维护等一系列管理中会出现的问题。 包括到小程序的代码包管理、小程序上下架…

Toolwtech Graphics3D.NET 2.0.x专业版Crack

您是否正在寻找一种易于使用、功能强大且 100% 托管的 .NET 组件来快速开发 3D 图形或 3D 数据可视化应用程序&#xff1f;请尝试 Graphics3D.NET。使用 Graphics3D.NET&#xff0c;您可以在几分钟内开发出专业的 3D 可视化应用程序。 Graphics3D.NET 特点&#xff1a; * 100% …

点亮 LED

1.在 Linux 系统下&#xff0c;一切皆文件&#xff01;应用层如何操控底层硬件&#xff0c;同样也是通过文件 I/O 的方式来实现。设备文件通常在/dev/目录下&#xff0c;我们也把/dev 目录下的文件称为设备节点。设备节点并不是操控硬件设备的唯一途径&#xff0c;除此之外&…

Vue模板语法(二)

Vue条件语句 1、v-if、v-else、v-else-if v-if、v-else、v-else-if用于根据条件来渲染某一块的内容&#xff1a; 这些内容只有在条件为true时&#xff0c;才会被渲染出来&#xff1b;这三个指令与JavaScript的条件语句if、else、else if类似&#xff1b; v-if的渲染原理&a…

单片机开发---ESP32-S3模块上手

背景介绍 想起来之前做的半成品单片机游戏机&#xff0c;又想继续做一个&#xff0c;不过之前那个单片机驱动屏幕速率太低&#xff0c;已经无法改进了。所以这次斥巨资购买了一款顶配的ESP32S开发板&#xff0c;做个简单的游戏机&#xff0c;没问题。 完整介绍链接 这花花绿绿…

通过logstash将Redis数据写入ElasticSearch

点击上方蓝字关注我使用logstash将Redis中数据自动同步至ES中1. 部署Redis上传编译好的Redis二级制安装包&#xff0c;使用redis用户启动redis即可具体编译安装过程可参考https://mp.weixin.qq.com/s/RaWy0sqRxcAti1qbv-GbZQ2. 部署logstash下载二进制安装包wget https://ar…

HTTP FLV交互流程及实例解析

HTTP FLV交互流程及实例解析 文章目录HTTP FLV交互流程及实例解析HTTP FLV传输方式HTTP FLV 抓包分析结束语HTTP FLV传输方式 前文已经介绍过&#xff0c;HTTP FLV利用了一个HTTP的协议约定&#xff0c;http 的content-length头字段如果不存在&#xff0c;则客户端就会一直接收…

ESP32-Camera性能(简单)测试评估

TOC 1. ESP32-Camera简介 最近因为接触了ESP32摄像头相关的资料和信息&#xff0c;稍微简单整理下&#xff0c;希望对该方案有兴趣的朋友可以有所帮助。 1.1 资料&信息 The Internet of Things wit ESP32ESP32-S Series开发模组Github: ESP32-Camera 1.2 ESP-EYE摄像头…

别忘记我:通过局部-全局内容建模进行文本擦除方法

本文简要介绍了发表于ECCV 2022的论文“Don’t Forget Me: Accurate Background Recovery for Text Removal via Modeling Local-Global Context”的相关工作。该论文针对文本擦除中存在的复杂背景修复的问题&#xff0c;提出了CTRNet&#xff0c;它利用局部和全局的语义建模提…

一个 go-sql-driver 的离奇 bug

文&#xff5c;郝洪范京东技术专家Seata-go 项目共同发起人微服务底层技术的探索与研究。本文 3482 字 阅读 7 分钟对于 Go CURD Boy 来说&#xff0c;相信 github.com/go-sql-driver/mysql 这个库都不会陌生。基本上 Go 的 CURD 都离不开这个特别重要的库。我们在开发 Seata-g…

LabVIEW将现有数据文件映射至TDMS数据文件格式

LabVIEW将现有数据文件映射至TDMS数据文件格式在某些情况下&#xff0c;可能无法使用TDMS文件格式&#xff0c;例如客户或供应商指定必须使用某种格式存储数据。有些传统仪器可能会自动使用某种自定义格式提供数据输出文件。此外&#xff0c;已经用某种方式收集的传统测量数据无…

PyQt6快速入门-自定义Widget

自定义Widget 文章目录 自定义Widget1、准备工作2、重写paintEvent事件3、Position策略4、更新显示5、绘制条形框5.1 绘制计算5.2 绘制条形框6、自定义样式7、添加鼠标交互能力8、完整代码QPainter是Qt中所有小部件绘制的基础。在本文中,详细介绍如何构建一个全新的自定义 GUI…

vue文本点击样式设置

vue文本点击样式设置嘚吧嘚干就完了光标边小手文本域样式修改hover语法语法一语法二语法三语法四学以致用&#xff0c;效果实现嘚吧嘚 相信当家在写代码的过程中&#xff0c;文本的点击事件是常有的吧&#xff0c;如历史搜索记录、页面跳转等。本次就就分享一下文本点击样式设…

从CES的亚马逊云科技展台,看云计算如何改变汽车行业

当云计算技术被广泛运用于智能汽车的制造&#xff0c;会给整个汽车行业带来怎样的变革&#xff1f;CES 2023汽车展区&#xff1a;亚马逊云科技展台成为焦点作为全球规模最大、影响力最为广泛的国际消费电子展&#xff0c;CES 2023于近日在美国拉斯维加斯圆满落下帷幕。在这场汇…

数据结构和算法的基本概念和基本术语(数据,数据元素,数据项,数据对象)

目录 一、数据结构的研究内容 1.1学生信息管理系统 1. 2人机对弈问题 1. 3最短路径问题 二、基本概念和术语 2.1数据&#xff0c;数据元素&#xff0c;数据项&#xff0c;数据对象 2.1.1 数据&#xff08;Data&#xff09;&#xff1a; 2.1.2 数据元素(Data Element)&a…

关于elasticsearch一些基本操作

哈喽~大家好&#xff0c;这篇来看看关于elasticsearch一些基本操作。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a; 【微服务】 &#x1f949;与这篇相关的文章&#xff1a; SpringCloud Se…

Import语句基础

1 问题 在 Java 中&#xff0c;如果给出一个完整的限定名&#xff0c;包括包名、类名&#xff0c;那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径&#xff0c;使得编译器可以找到某个类。 2 方法 1.import导入声明可分为两种: 1&a…

【每日一道智力题】之 轮流取石子(简单的尼姆博弈)

题目&#xff1a;一共有N颗石子&#xff08;或者其他乱七八糟的东西&#xff09;&#xff0c;每次最多取M颗最少取1颗&#xff0c;A&#xff0c;B轮流取&#xff0c;谁最后会获胜&#xff1f;&#xff08;假设他们每次都取最优解&#xff09;。解答&#xff1a;结论&#xff1a…

告诉大家几个好用的功能

功能一&#xff1a;打开通知面板/月历面板 WinN的作用是调出通知面板&#xff0c;由于Windows 11将月历与通知面板合在了一起&#xff0c;因此它的另一项功能&#xff0c;就是——打开月历。 功能二&#xff1a;WindowsW:启用小组件面板 如果我们需要用到系统自带的小组件&am…

WC2023游记

今年&#xff0c;我势必打破铜牌魔咒 Day -?~? 虽然已年及高二&#xff0c;但WC的讲课还是没有听懂多少&#xff0c;这段时间&#xff0c;北师大还有一名E队来我校训练&#xff0c;我只能感慨&#xff1a;“如果一个选手比你强&#xff0c;还比你小&#xff0c;那你就再也打…