目录

1. 红⿊树的概念

红⿊树的规则

红⿊树如何确保最⻓路径不超过最短路径的2倍的?

红⿊树的效率:

2. 红⿊树的实现

红⿊树的结构

红⿊树的插⼊

红⿊树树插⼊⼀个值的⼤概过程

情况1:变⾊

情况2:单旋+变⾊

情况3:双旋+变⾊

3. 红⿊树的插⼊代码实现

4. 红⿊树的查找

5. 红⿊树的验证

6. 结语


1. 红⿊树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。

红⿊树的规则

1. 每个结点不是红⾊就是⿊⾊

2. 根结点是⿊⾊的

3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的红⾊结点。

4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

说明:《算法导论》等书籍上补充了⼀条每个叶⼦结点(NIL)都是⿊⾊的规则。他这⾥所指的叶⼦结点不是传统的意义上的叶⼦结点,⽽是我们说的空结点,有些书籍上也把NIL叫做外部结点。NIL是为了⽅便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道⼀下这个概念即可。

红⿊树如何确保最⻓路径不超过最短路径的2倍的?

由规则4可知,从根到NULL结点的每条路径都有相同数量的⿊⾊结点,所以极端场景下,最短路径就就是全是⿊⾊结点的路径,假设最短路径⻓度为bh(black height)。

由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh。

综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh <= h <= 2*bh。

红⿊树的效率:

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么 h − 1 <= N < 2 ^2∗h − 1, 由此推出h logN ,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径2 ∗ logN,那么时间复杂度还O(logN)

红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格。

2. 红⿊树的实现

红⿊树的结构

// 枚举值表⽰颜⾊
enum Colour
{RED,BLACK
};
// 这⾥我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{// 这⾥更新控制平衡也要加⼊parent指针pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};
template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:
private:Node* _root = nullptr;
};

红⿊树的插⼊

红⿊树树插⼊⼀个值的⼤概过程

1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。

2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。

3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束

4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种情况分别处理。

说明:下图中假设我们把新增结点标识为c (cur),c的⽗亲标识为p(parent),p的⽗亲标识为g(grandfather),p的兄弟标识为u(uncle)

情况1:变⾊

c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。

分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊结点,g再变红,相当于保持g所在⼦树的⿊⾊结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新是因为,g是红⾊,如果g的⽗亲还是红⾊,那么就还需要继续处理;如果g的⽗亲是⿊⾊,则处理结束了;如果g就是整棵树的根,再把g变回⿊⾊。

情况1只变⾊,不旋转。所以⽆论c是p的左还是右,p是g的左还是右,都是上⾯的变⾊处理⽅式。

跟AVL树类似,图0我们展⽰了⼀种具体情况,但是实际中需要这样处理的有很多种情况。

图1将以上类似的处理进⾏了抽象表达,d/e/f代表每条路径拥有hb个⿊⾊结点的⼦树,a/b代表每 条路径拥有hb-1个⿊⾊结点的根为红的⼦树,hb>=0。

图2/图3/图4,分别展⽰了hb == 0/hb == 1/hb == 2的具体情况组合分析,当hb等于2时,这⾥组合 情况上百亿种,这些样例是帮助我们理解,不论情况多少种,多么复杂,处理⽅式⼀样的,变⾊再 继续往上处理即可,所以我们只需要看抽象图即可

情况2:单旋+变⾊

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

          g

    p        u

c

如果p是g的左,c是p的左,那么以g为旋转点进⾏右单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

        g

    u      p

               c

如果p是g的右,c是p的右,那么以g为旋转点进⾏左单旋,再把p变⿊,g变红即可。p变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为p的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

情况3:双旋+变⾊

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。

分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

          g

    p         u

      c

如果p是g的左,c是p的右,那么先以p为旋转点进⾏左单旋,再以g为旋转点进⾏右单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

        g

    u       p

       c

如果p是g的右,c是p的左,那么先以p为旋转点进⾏右单旋,再以g为旋转点进⾏左单旋,再把c变 ⿊,g变红即可。c变成课这颗树新的根,这样⼦树⿊⾊结点的数量不变,没有连续的红⾊结点了,且不需要往上更新,因为c的⽗亲是⿊⾊还是红⾊或者空都不违反规则。

3. 红⿊树的插⼊代码实现

// 旋转代码的实现跟AVL树是⼀样的,只是不需要更新平衡因⼦
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 (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);// 新增结点。颜⾊红⾊给红⾊cur->_col = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;// g// p uif (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// u存在且为红 -》变⾊再继续往上处理parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// u存在且为⿊或不存在 -》旋转+变⾊if (cur == parent->_left){// g// p u//c//单旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// c//双旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{// g// u pNode* uncle = grandfather->_left;// 叔叔存在且为红,-》变⾊即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为⿊{// 情况⼆:叔叔不存在或者存在且为⿊// 旋转+变⾊// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

4. 红⿊树的查找

按⼆叉搜索树逻辑实现即可,搜索效率为 O(logN)

Node* Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;
}

5. 红⿊树的验证

这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。

1. 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。

2. 规则2直接检查根即可

3. 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。

4. 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。

bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){// 前序遍历⾛到空时,意味着⼀条路径⾛完了//cout << blackNum << endl;if (refNum != blackNum){cout << "存在⿊⾊结点的数量不相等的路径" << endl;return false;}return true;}// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在连续的红⾊结点" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{if (_root == nullptr)return true;if (_root->_col == RED)return false;// 参考值int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);
}

6. 结语

这里分享一个b站Up主关于红黑树的视频资料~

红黑树 - 定义, 插入, 构建_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Xm421x7Lg/?spm_id_from=333.1387.search.video_card.click

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】MySQL去重查询详解

前言 在日常的数据库操作中&#xff0c;数据去重是一个非常常见的需求。无论是查询结果去重、数据清洗&#xff0c;还是统计分析&#xff0c;我们都需要掌握MySQL中的各种去重技术。本文将详细介绍MySQL中常用的去重关键字和操作方法&#xff0c;结合实际业务场景&#xff0c;帮…

Pinterest视觉营销自动化:亚矩阵云手机实例与多分辨率适配技术

Pinterest月活突破4.5亿的视觉经济时代&#xff0c;多分辨率适配与跨设备一致性成为品牌触达用户的核心挑战。传统营销因素材模糊、设备参数固化&#xff08;如固定分辨率1080P&#xff09;、行为机械化&#xff08;如定时批量上传&#xff09;&#xff0c;导致点击率低于行业均…

01数据结构-图的邻接矩阵和遍历

01数据结构-图的邻接矩阵和遍历1.图的遍历1.1深度优先遍历1.2广度优先搜索2.邻接矩阵的代码实现1.图的遍历 1.1深度优先遍历 深度优先搜索的过程类似于树的先序遍历&#xff0c;首先从例子中体会深度优先搜索&#xff0c;例如下图1是个无向图&#xff0c;采用深度优先算法遍历…

OpenAI发布的GPT-5 更新了哪些内容,它的核心能力有哪些?AI编码能力这么强,前端程序员何去何从?

目录**1. GPT-5的核心能力与技术突破****1.1 智能水平的质变****1.2 代码生成与优化****1.3 上下文处理与长文本能力****1.4 安全与可靠性改进****2. GPT-5的应用场景与案例****2.1 医疗领域****2.2 教育与学习****2.3 企业级应用****2.4 软件开发****3. 技术细节与创新****3.1…

【无标题】AI 赋能日常效率:实用案例与操作心得分享

大语言模型&#xff08;LLM&#xff09;早已不再是实验室里的专属品&#xff0c;而是逐渐渗透到我们工作与生活的方方面面。从繁琐的文档处理到复杂的信息筛选&#xff0c;从学习辅助到日常规划&#xff0c;AI 正以 "微生产力" 的形式重塑我们的效率边界。本文将分享…

Java-线程线程的创建方式

一.进程和线程进程&#xff1a;进程是资源分配的基本单位&#xff0c;每个进程都有自己独立的内存空间&#xff0c;可以看作是一个正在运行的程序实例线程&#xff1a;线程是CPU调度的基本单位&#xff0c;属于进程&#xff0c;一个进程可以包含多个线程。线程共享进程的内存空…

Electron 中 license-keys 的完整集成方案

secure-electron-license-keys 是一个专门为 Electron 应用设计的 npm 包&#xff0c;用于实现离线许可证密钥的创建、验证和管理&#xff0c;帮助开发者保护应用程序&#xff0c;确保只有拥有合法许可证的用户才能使用。以下是关于它的详细介绍&#xff1a; 在 Electron 应用中…

AI推理的“灵魂五问”:直面2025算力鸿沟与中国的破局之路

摘要&#xff1a;2025年&#xff0c;AI产业的重心已从训练全面转向推理&#xff0c;但一场严峻的“体验”危机正悄然上演。中美AI推理性能的巨大鸿沟&#xff0c;正让国内厂商面临用户流失的切肤之痛。本文以问答形式&#xff0c;直面当前中国AI产业在推理“最后一公里”上最尖…

2025 TexLive+VScode排版IEEE TGRS论文

2025 TexLiveVScode排版IEEE TGRS论文 本文主要内容&#xff1a; 软件安装 latex 排版 TRGS 论文期间遇到的问题 清晰图片导出 Latex公式、图、表、算法、参考文献的使用和引用 1. 前言 首先使用Overleaf网页版排版&#xff0c;但是后期排版图片太大&#xff0c;大小有限制&…

Redis数据组织方式

前言 Redis之所以高效&#xff0c;源自其优秀的架构设计。作为KV键值对存储数据库&#xff0c;数据的存储放在了内存中&#xff0c;KV键值对的组织方式更是其高效的原因之一。本文介绍其数据组织方式。 一、总体架构 在使用Redis时&#xff0c;服务端接收多个客户端的命令进行…

java组件安全vulhub靶场

>1--XStream1.打开靶场cd vulhub-master/xstream/CVE-2021-29505 docker up -d2.下载反序列化工具https://github.com/frohoff/ysoserial可以使用clone命令进行下载&#xff0c;也可以直接下载jar文件3.使用以下命令来开启脚本&#xff0c;将是反弹shell的语句进行base64编码…

UCMT部分复现

复现结果&#xff1a;88.03272&#xff0c;误差在接受范围内 补充信息 作者未解决后续报错问题&#xff0c;不建议复现

IntelliJ IDEA 新手全方位使用指南

摘要本文面向刚接触软件开发、使用 IntelliJ IDEA 的新手&#xff0c;详细介绍了 IDEA 的背景、版本区别、核心功能、运行原理、界面操作、项目管理、运行配置、以及 Git 版本控制基础。文章突出实用操作和理解流程&#xff0c;帮助新手快速熟悉IDEA环境&#xff0c;顺利完成项…

Python如何将图片转换为PDF格式

引言 在日常工作和学习中&#xff0c;我们经常需要将多张图片合并成一个PDF文件&#xff0c;以便于分享或打印。Python提供了多种库来实现这一需求&#xff0c;本文将详细介绍三种常用的方法&#xff1a;img2pdf库、Pillow库和PyMuPDF库&#xff0c;并附上完整的代码示例。 方法…

Python如何合并两个Excel文件

引言 在日常数据处理中&#xff0c;合并Excel文件是常见需求。Python提供了多种库&#xff08;如pandas、openpyxl&#xff09;来实现这一操作。本文将详细介绍两种主流方法&#xff0c;并附上完整代码示例&#xff0c;帮助您高效完成Excel合并任务。 方法一&#xff1a;使用pa…

【SQL进阶】用EXPLAIN看透SQL执行计划:从“盲写“到“精准优化“

用EXPLAIN洞察SQL执行计划&#xff1a;从"盲目编写"到"精准优化" 很多开发者在编写SQL时仅凭直觉&#xff0c;直到查询超时才发现问题。MySQL内置的EXPLAIN工具能提前揭示查询执行逻辑&#xff0c;帮助预防性能隐患。本文将带你掌握EXPLAIN的核心用法&…

电影艺术好,电影知识得学

关于电影应该谈什么导演风格、演员技术、剧本结构、票房、政治因素等。一、纸上谈电影电影制作期&#xff1a;研发、前制、拍摄、后制、发行。一般成员只在某个时期出现。制片和导演会从头监督到尾。研发期&#xff1a; 剧本概念发想与成形的时期。创作自由度比较大&#xff0c…

FPGA学习笔记——简易的DDS信号发生器

目录 一、任务 二、分析 三、ROM IP核配置 四、Visio图 五、代码 &#xff08;1&#xff09;.v代码 &#xff08;2&#xff09;仿真代码 六、仿真 七、实验现象 一、任务 用串口模块&#xff0c;用上位机发送指令&#xff0c;FPGA接收&#xff0c;然后输出对应的波形&…

在NVIDIA Orin上用TensorRT对YOLO12进行多路加速并行推理时内存泄漏 (中)

接上篇 在NVIDIA Orin上用TensorRT对YOLO12进行多路加速并行推理时内存泄漏&#xff08;上&#xff09; 通过上篇的分析&#xff0c;发现问题在采集数据到传入GPU之前的阶段。但随着新一轮长时间测试发现&#xff0c;问题依然存在。 如上图&#xff0c;在运行20多分钟内存开始…

计数组合学7.17(Murnaghan–Nakayama 规则 )

7.17 Murnaghan–Nakayama 规则 我们已经成功地用基 mλm_\lambdamλ​、hλh_\lambdahλ​ 和 eλe_\lambdaeλ​ 表示了 Schur 函数 sλs_\lambdasλ​。本节我们将考虑幂和对称函数 pλp_\lambdapλ​。一个斜分划 λ/μ\lambda / \muλ/μ 是连通的&#xff0c;如果其分拆图…