当前位置: 首页 > article >正文

C++ 红黑树:从规则到实现,手把手带你写一棵红黑树

红黑树是二叉搜索树家族中重要的一员在 C STL 的map和set底层、Linux 内核的调度器、Java 的TreeMap等地方都能看到它的身影。它通过一套精妙的颜色规则在频繁的插入删除中维持着近似平衡既保证了O(log N)的时间复杂度又比 AVL 树拥有更少的旋转次数。一、什么是红黑树红黑树本质上是一棵二叉搜索树但它的每个结点都增加了一个颜色属性只能是红色或黑色。通过下面四条严格的规则红黑树能够保证没有任何一条从根到叶子的路径会比其他路径长出 2 倍从而实现近似平衡。1.1 红黑树的四条铁律结点非红即黑— 每个结点的颜色要么是红色要么是黑色。根结点必为黑色— 树的根结点始终是黑色的。不连续红色— 如果一个结点是红色的那么它的两个孩子都必须是黑色的。也就是说任意一条从根到叶子的路径上不会出现连续的两个红色结点。黑高相同— 对于任意一个结点从它到其所有后代叶子结点NIL 或 NULL 结点的简单路径上黑色结点的数量必须相同。补充在一些经典教材如《算法导论》中会把叶子结点NIL也视为外部结点并强制为黑色这主要是为了让“路径”的定义更加一致。在实际编码中我们通常用 NULL 作为结束标志并假设它也符合黑色规则不影响平衡的判断。1.2 为什么最长路径不会超过最短路径的 2 倍这是红黑树最核心的平衡保证。我们可以从下图中的极端情况来理解根据规则 4每条路径上的黑色结点数量相同记作bhblack height。根据规则 2 和规则 3红色结点不能连续出现因此路径中最多的红色结点就是和黑色结点交替排列即最长路径由“黑—红—黑—红……”组成长度最多为2 * bh。最短路径则全是黑色结点长度为bh。因此任意一条路径长度h满足bh h 2 * bh。这就保证了整棵树的高度始终被控制在对数级别从而保证了增删查改的时间复杂度都是O(log N)。1.3 红黑树 vs AVL 树AVL 树通过记录每个结点的平衡因子左右子树高度差不超过 1来严格控制平衡因此查询性能非常极致但插入和删除时可能需要更多的旋转来恢复平衡。红黑树的设计更“宽容”一些它不追求绝对平衡而是保证最长路径不超过最短路径的 2 倍。这使得红黑树在插入相同数量结点时旋转次数通常比 AVL 树少也因此更适合插入、删除操作非常频繁的场景。二、红黑树的结构定义在代码实现中我们采用 key-value 结构的泛型模板同时为每个结点增加颜色枚举以及指向父亲的_parent指针方便后续调整。// 颜色枚举 enum Colour { RED, BLACK }; // 红黑树结点 templateclass K, class V struct RBTreeNode { pairK, V _kv; RBTreeNodeK, V* _left; RBTreeNodeK, V* _right; RBTreeNodeK, V* _parent; Colour _col; RBTreeNode(const pairK, V kv) : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) { } }; // 红黑树类 templateclass K, class V class RBTree { typedef RBTreeNodeK, V Node; public: // 插入、查找、验证等接口 bool Insert(const pairK, V kv); Node* Find(const K key); bool IsBalance(); private: Node* _root nullptr; // 旋转函数与 AVL 树相同只是不更新平衡因子 void RotateL(Node* parent); void RotateR(Node* parent); // 验证辅助函数 bool Check(Node* root, int blackNum, const int refNum); };三、红黑树的插入 —— 核心难点插入操作可以概括为以下几步按照二叉搜索树的规则将新结点插入到正确位置。新结点默认染成红色。这是因为如果是黑色一定会破坏规则 4改变路径上的黑高维护起来代价巨大而插入红色结点只有可能破坏规则 3连续红色相对更容易修正。如果父亲结点是黑色直接结束不需要任何调整。如果父亲结点是红色违反规则 3则需要根据“叔叔结点”即父结点兄弟的颜色和状态分三种情况处理。我们约定c 当前结点curp 父结点g 祖父结点u 叔叔结点。3.1 情况一叔叔存在且为红色 —— 变色就能解决当p红、u红、g黑时我们只需将p和u染黑将g染红将当前处理结点c移动到g继续往上检查。理解p和u变黑相当于在各自子树增加一个黑色结点g变红相当于维持原路径黑高不变。但g变红后可能与更上层的红结点冲突因此需要循环向上更新。如果最后g是根我们再强行把它染回黑色。无论p位于g的左边还是右边c是p的左还是右处理方式完全一样只涉及变色不需要旋转。// 情况一叔叔存在且为红 if (uncle uncle-_col RED) { parent-_col uncle-_col BLACK; grandfather-_col RED; // 继续向上调整 cur grandfather; parent cur-_parent; }3.2 情况二 情况三叔叔不存在或为黑色 —— 旋转变色若u不存在或颜色为黑单纯的变色已经无法解决连续红色问题这时候必须借助旋转。根据p和c的相对位置又细分为单旋和双旋两种3.2.1 单旋场景直线型p是g的左孩子c是p的左孩子→ 对g进行右单旋p是g的右孩子c是p的右孩子→ 对g进行左单旋旋转完毕后将p染黑、g染红。此时p成为新子树的根整体黑高不变且解决了连续红色问题不需要再向上迭代。// 情况二单旋p 为 g 的左c 为 p 的左 if (cur parent-_left) { RotateR(grandfather); parent-_col BLACK; grandfather-_col RED; } // p 为 g 的右c 为 p 的右 if (cur parent-_right) { RotateL(grandfather); parent-_col BLACK; grandfather-_col RED; }3.2.2 双旋场景折线型p是g的左孩子c是p的右孩子→ 先对p左单旋再对g右单旋p是g的右孩子c是p的左孩子→ 先对p右单旋再对g左单旋旋转后将c染黑、g染红。此时c变成了新子树的根同样黑高不变且不需要继续向上调整。// 情况三双旋p 为 g 的左c 为 p 的右 else { RotateL(parent); RotateR(grandfather); cur-_col BLACK; grandfather-_col RED; } // p 为 g 的右c 为 p 的左 else { RotateR(parent); RotateL(grandfather); cur-_col BLACK; grandfather-_col RED; }3.3 插入操作完整代码结合以上所有情况插入函数的伪代码框架如下bool Insert(const pairK, V kv) { // 1. 空树新建黑结点作为根 if (_root nullptr) { _root new Node(kv); _root-_col BLACK; return true; } // 2. 二叉搜索树查找插入位置 Node* parent nullptr; Node* cur _root; while (cur) { parent cur; if (kv.first cur-_kv.first) cur cur-_left; else if (kv.first cur-_kv.first) cur cur-_right; else return false; // 已存在 } // 3. 新建红色结点挂在父结点下 cur new Node(kv); cur-_col RED; if (kv.first parent-_kv.first) parent-_left cur; else parent-_right cur; cur-_parent parent; // 4. 调整红黑树 while (parent parent-_col RED) { Node* grandfather parent-_parent; if (parent grandfather-_left) { Node* uncle grandfather-_right; // 情况一叔叔红 - 变色 if (uncle uncle-_col RED) { parent-_col uncle-_col BLACK; grandfather-_col RED; cur grandfather; parent cur-_parent; } else { // 叔叔黑或不存在 if (cur parent-_left) { // 单旋右 RotateR(grandfather); parent-_col BLACK; grandfather-_col RED; } else { // 双旋左右 RotateL(parent); RotateR(grandfather); cur-_col BLACK; grandfather-_col RED; } break; // 旋转后结构稳定可退出 } } else { // 对称情况parent 是祖父的右孩子 Node* uncle grandfather-_left; if (uncle uncle-_col RED) { parent-_col uncle-_col BLACK; grandfather-_col RED; cur grandfather; parent cur-_parent; } else { if (cur parent-_right) { RotateL(grandfather); parent-_col BLACK; grandfather-_col RED; } else { RotateR(parent); RotateL(grandfather); cur-_col BLACK; grandfather-_col RED; } break; } } } // 5. 强制根为黑 _root-_col BLACK; return true; }旋转函数与 AVL 树完全一致只需要改变指针指向即可这里不再赘述。3.4 为什么“旋转变色”后就可以直接退出因为经过单旋或双旋后新的子树根结点被染黑的那个代替了原来的g它的颜色一定是黑色。这样一来新根与上层的颜色连接断然不会再出现“连续红色”整棵树的平衡已经恢复所以可以break不再继续向上调整。四、红黑树的查找查找操作完全沿用二叉搜索树的特性复杂度O(log N)。Node* Find(const K key) { Node* cur _root; while (cur) { if (key cur-_kv.first) cur cur-_left; else if (key cur-_kv.first) cur cur-_right; else return cur; } return nullptr; }五、红黑树的验证 —— 你的树真的“红黑”吗写完插入后我们需要一套可靠的验证工具而不是凭感觉判断。直接套用四条规则颜色只能为红或黑 → 枚举保证了这一点。根是黑色。不能有连续红色结点 → 可以用前序遍历检查反向检查父亲颜色更方便若当前结点为红且父亲也为红则违规。每条路径黑高相同 → 先通过最左边一条路径统计出一个参考黑高refNum然后在前序遍历每条路径时累计黑色结点数走到空时对比。bool Check(Node* root, int blackNum, const int refNum) { if (root nullptr) { // 一条路径走完比较黑高 if (blackNum ! refNum) { 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); }只要IsBalance()返回true就意味着我们的红黑树完全遵守了所有规则平衡性自然就得到了保证。六、红黑树的删除了解红黑树的删除比插入更加复杂涉及更多颜色的互换、兄弟结点的多重判断以及可能的二次调整。本文暂不作深入展开感兴趣的同学可以阅读《算法导论》或《STL 源码剖析》中的相关章节。七、总结红黑树通过简单的颜色规则以“不连续红”“黑高相等”为约束保证树的高度始终在log N2log N之间从而获得稳定的O(log N)增删查改性能。它的实现核心在插入调整叔叔红色只变色向上迭代叔叔黑色/不存在 直线单旋 变色调整结束叔叔黑色/不存在 折线双旋 变色调整结束。与 AVL 树相比红黑树的平衡条件更宽松旋转次数更少特别适合写多读多或频繁插入删除的场景。掌握红黑树不仅加深了对自平衡搜索树的理解更是窥见了许多工业级数据结构的底层设计哲学。如果你觉得这篇文章对你有帮助欢迎点赞、收藏也欢迎在评论区交流你的理解与困惑我们一起进步

相关文章:

C++ 红黑树:从规则到实现,手把手带你写一棵红黑树

红黑树是二叉搜索树家族中重要的一员,在 C STL 的 map 和 set 底层、Linux 内核的调度器、Java 的 TreeMap 等地方都能看到它的身影。它通过一套精妙的颜色规则,在频繁的插入删除中维持着近似平衡,既保证了 O(log N) 的时间复杂度&#xff0c…...

网络-堆叠

堆叠链路聚合:多条物理链路变成一条逻辑链路堆叠:多个支持堆叠特性的交换机,通过堆叠技术,变成一台逻辑上的交换机CSS(集群):用于框式交换机,只支持 2 台设备,从逻辑上虚…...

过去我父亲骑骆驼,现在我开汽车,将来我儿子驾驶喷气式飞机,最后他的儿子只能骑骆驼。——沙特阿拉伯谚语

这句沙特阿拉伯谚语有着丰富的内涵,具体可以从这几个角度理解:对发展循环的调侃‌ 它以交通工具的变迁为线索,描绘了一个看似“进步”的循环:从骑骆驼到开汽车,再到驾驶喷气式飞机,最后又回到骑骆驼。用夸张…...

5分钟快速上手:终极通达信缠论可视化插件指南

5分钟快速上手:终极通达信缠论可视化插件指南 【免费下载链接】Indicator 通达信缠论可视化分析插件 项目地址: https://gitcode.com/gh_mirrors/ind/Indicator 缠论作为股票技术分析领域的核心理论,以其严谨的逻辑结构和独特的市场视角成为众多交…...

Pearcleaner:彻底告别Mac臃肿,三步释放宝贵存储空间

Pearcleaner:彻底告别Mac臃肿,三步释放宝贵存储空间 【免费下载链接】Pearcleaner A free, source-available and fair-code licensed mac app cleaner 项目地址: https://gitcode.com/gh_mirrors/pe/Pearcleaner 你是否曾发现,即使删…...

如何彻底清理你的Mac:Pearcleaner智能卸载工具完全指南

如何彻底清理你的Mac:Pearcleaner智能卸载工具完全指南 【免费下载链接】Pearcleaner A free, source-available and fair-code licensed mac app cleaner 项目地址: https://gitcode.com/gh_mirrors/pe/Pearcleaner 还在为Mac上堆积的应用残留文件而烦恼吗&…...

John the Ripper 的 --format=crypt:让系统替你算哈希

在使用 John the Ripper(以下简称 John)破解密码哈希时,你可能会遇到这样的情况:John 自动检测不到哈希类型,或者报错说找不到对应的格式插件。这时候,一个"万能兜底"的参数就能派上用场——--fo…...

完全免费!3个步骤让你的Windows电脑风扇变智能,告别噪音烦恼

完全免费!3个步骤让你的Windows电脑风扇变智能,告别噪音烦恼 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/G…...

NVIDIA Profile Inspector深度解析:如何解锁显卡隐藏性能的完整指南

NVIDIA Profile Inspector深度解析:如何解锁显卡隐藏性能的完整指南 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector 你是否曾经感觉自己的NVIDIA显卡性能被封印?明明配置不差&am…...

各省市区县地形位置指数平均值、最大值、最小值和标准差数据(地形起伏度)

各省市区县地形位置指数平均值、最大值、最小值和标准差数据(地形起伏度) 数据原始来源于 NASA ASTER Global Digital Elevation Model V003 版数据。 地形位置指数通常也称为地形起伏度,即在一个特定区域内最高点与最低点海拔高度的差值。…...

从一根琴弦到万物波动:用Python和NumPy手把手复现Fourier级数的诞生过程

从一根琴弦到万物波动:用Python和NumPy手把手复现Fourier级数的诞生过程 当18世纪的数学家们争论"不连续函数能否用三角级数表示"时,他们或许想象不到两个世纪后的开发者只需几行代码就能可视化这个革命性思想。本文将带您穿越时空&#xff0c…...

组合优化中的在线学习算法:Exp3与FTRL详解

1. 组合优化中的在线学习算法概述组合优化问题在计算机科学和运筹学中无处不在,从经典的旅行商问题(TSP)到背包问题,再到资源分配和调度问题。这类问题的共同特点是需要在离散的、通常是巨大的解空间中寻找最优或近似最优的解。传统方法如动态规划、分支…...

通达信VOL实战监测:一个能替代成交量指标的源码,手把手教你安装与解读

通达信VOL指标深度解析:从源码安装到实战应用全指南 在股票技术分析领域,成交量指标(VOL)一直被视为价格变动的重要验证工具。传统成交量指标虽然直观,但缺乏对市场情绪的分级判断。今天我们要探讨的这套通达信VOL增强指标,通过换…...

Windows蓝屏0xE6?别慌!手把手教你用WinDbg定位DRIVER_VERIFIER_DMA_VIOLATION元凶(以NVIDIA显卡驱动为例)

Windows蓝屏0xE6故障排查:用WinDbg精准定位DRIVER_VERIFIER_DMA_VIOLATION问题 当你正专注于重要工作时,屏幕突然蓝屏并显示"DRIVER_VERIFIER_DMA_VIOLATION (0xE6)"错误代码,这种经历足以让任何Windows用户感到沮丧。这种错误通常…...

观察 Taotoken 在多模型聚合调用下的路由稳定性与响应表现

观察 Taotoken 在多模型聚合调用下的路由稳定性与响应表现 1. 测试环境与配置 本次测试基于 Taotoken 平台的标准 API 接入环境,使用 Python SDK 进行多模型调用。在控制台配置了三个不同供应商的模型作为备用路由选项,模型选择策略设置为自动模式。测…...

观察 Taotoken 按 Token 计费模式下的成本控制效果

观察 Taotoken 按 Token 计费模式下的成本控制效果 1. 项目背景与计费需求 在涉及大模型调用的项目中,成本控制一直是团队管理者关注的核心问题。传统按次或包月计费模式往往难以精确匹配实际使用量,容易造成资源浪费或预算超支。我们团队近期接入了 T…...

DROID-SLAM的“可微分BA层”到底强在哪?深入拆解RAFT与LieTorch的协同设计

DROID-SLAM的可微分BA层技术解析:RAFT与LieTorch的协同创新 视觉SLAM领域近年来最引人注目的突破之一,莫过于深度学习与传统几何方法的深度融合。DROID-SLAM作为这一交叉领域的代表性工作,其核心创新点——可微分稠密束调整(DBA&a…...

用AT32F437的QSPI给项目扩容:手把手实现华邦W25N01G NAND Flash的文件系统移植

AT32F437 QSPI扩展实战:W25N01G NAND Flash文件系统深度整合指南 在嵌入式系统开发中,存储扩展一直是提升设备能力的关键路径。当AT32F437这类高性能MCU遇到1Gb大容量NAND Flash时,如何突破基础驱动层面,实现稳定可靠的文件系统支…...

对比直接使用厂商API体验Taotoken在路由容灾上的便利

服务波动下的无缝切换:Taotoken 路由容灾实践观察 1. 背景与问题场景 在实际开发过程中,依赖单一模型供应商的 API 服务存在潜在风险。当供应商出现临时性服务波动或区域性故障时,传统解决方案通常需要开发者手动切换 API 端点或模型&#…...

《图灵完备》迷宫机器人避坑指南:为什么‘右手扶墙’算法会失效?以及如何用汇编实现它

《图灵完备》迷宫机器人避坑指南:从算法失效到汇编实战 当你第一次在《图灵完备》的迷宫关卡中尝试"右手扶墙"算法时,可能会惊讶地发现这个经典方法在某些情况下会彻底失效。这不是算法的错,而是游戏机制与真实世界物理规则的微妙差…...

Cadence IC617下tsmc18rf与tsmcN65工艺库安装避坑全记录(附转换失败备用包)

Cadence IC617工艺库安装实战:从CDB-OA转换失败到应急方案全解析 在半导体设计领域,工艺库的安装是每位工程师必须掌握的基础技能。当面对Cadence IC617环境下tsmc18rf与tsmcN65工艺库的安装时,许多用户会发现即使严格遵循教程步骤&#xff0…...

告别电源纹波!手把手教你用UCC28019设计一个高效率PFC模块(附完整原理图与BOM清单)

告别电源纹波!手把手教你用UCC28019设计一个高效率PFC模块(附完整原理图与BOM清单) 在中小功率开关电源设计中,功率因数校正(PFC)模块的性能直接影响整个系统的效率和稳定性。传统设计往往面临纹波大、动态…...

实战指南:构建智能缠论量化分析的高效开源方案

实战指南:构建智能缠论量化分析的高效开源方案 【免费下载链接】Indicator 通达信缠论可视化分析插件 项目地址: https://gitcode.com/gh_mirrors/ind/Indicator 你是否厌倦了手动绘制缠论线段和中枢的繁琐过程?CZSC.dll开源缠论量化插件通过先进…...

ROS导航调参实战:如何让你的TurtleBot3在复杂办公室环境里不撞墙?

ROS导航调参实战:TurtleBot3复杂环境避障优化指南 在机器人导航领域,ROS的move_base功能包提供了强大的路径规划能力,但默认参数往往难以应对真实场景中的复杂环境。当你的TurtleBot3在办公室走廊频繁撞墙、在U型转弯处卡住、或对动态障碍反应…...

2025届毕业生推荐的五大AI论文工具推荐榜单

Ai论文网站排名(开题报告、文献综述、降aigc率、降重综合对比) TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 要降低文章里人工智能生成的那种痕迹,得从词汇的挑选、句式的构造以及逻辑的连贯…...

芯片版图设计避坑指南:那些藏在Metal走线里的寄生电容,我是这样处理的

芯片版图设计避坑指南:那些藏在Metal走线里的寄生电容,我是这样处理的 在芯片设计的微观世界里,版图工程师的每一个决策都可能引发蝴蝶效应。记得第一次独立负责高速SerDes模块时,我在Metal6层精心布置的差分对信号线,…...

从手机到汽车:拆解AFE芯片ADBMS6832,看电池安全监控如何进化

从手机到汽车:拆解AFE芯片ADBMS6832,看电池安全监控如何进化 你是否曾在寒冬中掏出手机,却发现电量从50%瞬间归零自动关机?或是驾驶电动车时,明明电量充足却遭遇加速无力的窘境?这些现象背后,隐…...

AI模型选型实战:基于开源工具llmarena.ai的成本与性能对比

1. 项目概述:一个为开发者而生的AI模型比价与选型工具在AI应用开发这个行当里摸爬滚打了几年,我最大的感触就是“选择困难症”越来越严重了。早些年,大家基本就盯着OpenAI的API,GPT-3.5够用,GPT-4更强,没太…...

别再复制粘贴了!解决Maven+Jacoco不生成.exec文件的正确姿势(附完整POM配置)

MavenJacoco覆盖率报告生成实战:从原理到配置的完整避坑指南 最近在团队内部做代码质量审计时,发现一个有趣的现象:超过60%的Java项目虽然配置了Jacoco覆盖率检测,但实际并未正确生成.exec数据文件。更令人惊讶的是,大…...

同济线代第七版笔记:从期末突击到AI应用,我的矩阵恐惧症治愈之路

同济线代第七版笔记:从期末突击到AI应用,我的矩阵恐惧症治愈之路 第一次翻开同济版《线性代数》时,那些密密麻麻的矩阵和行列式就像天书符号。直到在机器学习课程中看到反向传播算法的推导过程,我才突然意识到——原来这些"吓…...