【 C++ 】红黑树

发布时间:2026/10/17 14:27:10

【 C++ 】红黑树 目录1、红黑树的概念2、红黑树的性质3、红黑树节点的定义4、红黑树类 的基本框架5、红黑树的插入操作6、红黑树的验证7、红黑树的删除8、红黑树与AVL树的比较9、红黑树的应用1、红黑树的概念红黑树是一种二叉搜索树但在每个结点上增加一个存储位表示结点的颜色可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制红黑树确保没有一条路径会比其他路径长出俩倍最长路径不超过最短路径的2倍因而是接近平衡的。同是二叉搜索平衡树但是AVL树控制的比红黑树严格的多AVL树要是每个节点的平衡因子绝对值不超过1就会导致不断的去旋转调整付出相对较高的代价而这里红黑树更像是一种近似平衡条件没有这么苛刻。如下一棵树站在红黑树的角度看是平衡的站在AVL树的角度看就是不平衡的需要旋转调整但是从搜索效率的角度看AVL树还是好一点因为它的平衡标准高就导致其更加平衡相同数量的节点情况下AVL树的高度会更低加上存100w个数据AVL树大概有20层log100w而红黑树最坏就能达到40层显然AVL树的搜索效率高。但是在内存里找20次和找40次没有什么区别因为CPU足够的快这里简单提一下。2、红黑树的性质1、每个结点不是红色就是黑色2、根节点必须是黑色的3、如果一个节点是红色的则它的两个孩子结点是黑色的没有连续的红色节点4、对于每个结点从该结点到其所有后代叶结点的简单路径上均包含相同数目的黑色结点每条路径都包含相同数量的黑色节点5、每个叶子结点都是黑色的(此处的叶子结点指的是空结点 -》NIL节点)根据这些规则红黑树是如何保证最长路径不超过最短路径的2倍的呢首先我们根据规则分析得知我们假设一条路径的黑色节点的个数为N个则最长路径和最短路径的情况如下最短路径全黑最长路径一黑一红间隔而这里一黑一红间隔的原因在于红黑树不允许出现连续的红节点为了能最大程度的保证最长节点数唯有一黑一红间隔的方式才能达到最长综上当黑节点个数固定为N时最短路径节点个数为N最长路径节点个数为2N3、红黑树节点的定义这里节点的实现相较于AVL树我们依旧是创建成KV模型、三叉链结构唯一有所改变的是这里要通过枚举的方式把红色和黑色定义好并在节点类内部定义变量_col表示节点颜色最后记得写上构造函数。enum Colour { Red, Black, }; //节点类 template class K, class V struct RBTreeNode { //三叉链结构 RBTreeNodeK, V* _left; RBTreeNodeK, V* _right; RBTreeNodeK, V* _parent; //存储的键值对 pairK, V _kv; //节点的颜色 Colour _col; //构造函数 RBTreeNode(const pairK, V kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) , _col(Red) {} };为什么插入的节点在构造函数这里要处理成红色如果处理成黑色则一定导致新插入节点的那条路径多出一个黑色节点不再满足各个路径黑色节点个数相同的性质一定破坏性质4此时很难维护。如果处理成红色则可能父亲节点也是红色此时就出现了连续的红色节点破坏性质3不过此时我们向上调整即可但如果父亲节点是黑色那就无需操作了不违反任何性质。综合利弊插入黑色节点一定会破坏性质4而插入红色节点可能破坏性质3因此处理成红色为宜。4、红黑树类 的基本框架此模板类主要是用于红黑树的插入、旋转、调整、验证等等操作基本框架如下//红黑树的类 template class K, class V class RBTree { typedef RBTreeNodeK, V Node; public: //…… private: Node* _root nullptr; };5、红黑树的插入操作红黑树的插入操作主要分为这几大步骤1、一开始为空树直接new新节点2、一开始非空树寻找插入的合适位置3、找到插入的合适位置后进行父亲与孩子的双向链接4、检测新节点插入后红黑树的性质是否造到破坏接下来对其进行逐个分析1、一开始为空树直接new新节点因为树为空的所以直接new一个新插入的节点将其作为根_root即可接着更新颜色_col为黑色。2、一开始非空树寻找插入的合适位置这里和二叉搜索树的寻找合适的插入位置的思想一样都要遵循以下几步插入的值 节点的值更新到右子树查找插入的值 节点的值更新到左子树查找插入的值 节点的值数据冗余插入失败返回false当循环结束的时候就说明已经找到插入的合适位置即可进行下一步链接。3、找到插入的合适位置后进行父亲与孩子的双向链接注意这里节点的构成为三叉链因此最后链接后端孩子和父亲是双向链接具体操作如下插入的值 父亲的值把插入的值链接在父亲的右边插入的值 父亲的值把插入的值链接在父亲的左边因为是三叉连插入后记得双向链接孩子链接父亲走到这说明节点已经插入完毕接下来就要对红黑树的颜色进行调整了4、检测新节点插入后红黑树的性质是否造到破坏不是所有的情况都是需要进行调整的当插入节点的父亲为黑色新节点的默认颜色是红色那么就不需要进行调整因为没有破坏红黑树的任何一条性质。只有当插入节点的父亲为红色时新节点的默认颜色也是是红色才需要进行调整因为此时插入的节点和父亲都是红色节点但是红黑树不允许出现连续的红色节点此时就要进行调整。注意这里既然插入节点cur的父亲p是红色那么根据红黑树的性质根结点是黑色的其父亲的父亲g也就是祖父必然存在且一定是黑色那么其父亲的兄弟节点u可能不存在也就是新插入节点cur的叔叔。因此我们约定cur为当前节点p为父节点g为祖父节点u为叔叔节点。这里调整的办法主要是看叔叔节点的颜色如何叔叔节点的不同会导致三种不同的情况需要调整情况一cur为红p为红g为黑u存在且为红情况二cur为红p为红g为黑u不存在情况三cur为红p为红g为黑u存在且为黑接下来分别进行讨论情况一cur为红p为红g为黑u存在且为红为了避免出现连续的红色节点我们可以把父节点p变黑但是为了保证每条路径的黑色节点个数相同我们需要把祖父节g点变红不影响其它路径黑节点的个数再把叔叔节点u变黑。调整并未结束此时祖父节点g为红色但是如果这棵树本就是一颗完整的树呢也就是g为根节点那么只需要把节点g变成黑色即可。如果这棵树是一棵树的子树那么刚好把祖父节点g作为新插入的节点cur向上继续调整继续判断父亲、叔叔如何……直至调整结束。补充情况一不关心左右关系只变色不旋转所以 p、u是g的左或右是无所谓的cur是p的左或右也是无所谓的。情况二cur为红p为红g为黑u不存在如果节点u不存在则cur一定是新插入节点因为如果cur不是新插入节点则cur和p一定有一个节点的颜色是黑色就不满足性质4每条路径黑色节点个数相同。此时就是一个很经典的右单旋结构新节点插入较高左子树的左侧我们可以先对其进行一个右单旋再来更新颜色。具体步骤如下让祖父g变成父亲p的右子树父亲p作为根节点更新父亲节点p为黑色更新祖父g为红色补充如若p为g的右孩子cur为p的右孩子则针对p做左单旋转示例如若祖孙三代的关系是折线cur、parent、grandfather这三个结点为一条折现则我们需要先进行双旋操作再进行颜色调整颜色调整后这棵被旋转子树的根是黑色的因此无需继续往上进行处理。示例综上p为g的左cur为p的左则进行右单旋 p变黑g变红p为g的右cur为p的右则进行左单旋 p变黑g变红p是g的左cur是p的右则进行左右双旋 cur变黑 g变红p是g的右cur是p的左则进行右左双旋 cur变黑 g变红下面进入情况三情况三cur为红p为红g为黑u存在且为黑此情况绝非单独存在绝不可能是真的新节点cur插入然后还会出现p为红g为黑u存在且为黑的情况如果存在那么只能说明先前插入节点或者构造函数就有问题因为插入前就不符合红黑树的性质啊每个路径的黑节点个数均相同既然情况三出现了那么一定是合理的它就是建立在情况一的基础上继续往上调整从而出现的一种特殊情况具体咱就是画图演示此时就是很明显的一个情况3了cur为红pp为红gg为黑u存在且为黑由此证明情况三是通过情况一向上继续调整演化出来的。并且此新节点一定是从p和x任意一颗左右子树插入或演化上来的才引发后续的cur从黑变红。此时就是一个很经典的右单旋结构cur在较高左子树的左侧我们可以先对其进行一个右单旋再来更新颜色。具体步骤如下让p的右子树变成g的左子树让p变成根节点位置p的右子树指向g更新p的颜色为黑色更新g的颜色为红色补充如若p为g的右孩子cur为p的右孩子则进行左单旋 调色示例综上p为g的左cur为p的左则进行右单旋 p变黑g变红p为g的右cur为p的右则进行左单旋 p变黑g变红p是g的左cur是p的右则进行左右双旋 cur变黑 g变红p是g的右cur是p的左则进行右左双旋 cur变黑 g变红情况二和情况三旋转 变色后这颗子树不违反红黑树规则相比插入前且黑色节点的数量不变不会影响上层处理结束了。代码如下bool Insert(const pairK, V kv) { //1、一开始为空树直接new新节点 if (_root nullptr) { _root new Node(kv); _root-_col Black;//新插入的节点处理成黑色 return true; } //2、寻找插入的合适位置 Node* cur _root; Node* parent nullptr; 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;//插入的值 节点的值数据冗余插入失败返回false } } //3、找到了插入的位置进行父亲与插入节点的链接 cur new Node(kv); cur-_col Red;//插入的节点处理成红色 if (parent-_kv.first kv.first) { parent-_right cur;//插入的值 父亲的值链接在父亲的右边 } else { parent-_left cur;//插入的值 父亲的值链接在父亲的左边 } cur-_parent parent;//三叉链要双向链接 //4、检测新节点插入后红黑树的性质是否造到破坏 while (parent parent-_col Red)//存在连续的红色节点 { Node* grandfather parent-_parent; assert(grandfather); //先确保叔叔的位置 if (grandfather-_left parent) { Node* uncle grandfather-_right; //情况一cur为红p为红g为黑u存在且为红 if (uncle uncle-_col Red) { //变色 parent-_col uncle-_col Black; grandfather-_col Red; //继续往上处理 cur grandfather; parent cur-_parent; } //情况二情况三叔叔不存在或者叔叔存在且为黑 else { if (cur parent-_left)//p为g的左cur为p的左则进行右单旋 p变黑g变红 { // g // p // cur RotateR(grandfather); parent-_col Black; grandfather-_col Red; } else//p是g的左cur是p的右则进行左右双旋 cur变黑 g变红 { // g // p // cur RotateLR(grandfather); cur-_col Black; grandfather-_col Red; } break; } } else//grandfather-_right parent { Node* uncle grandfather-_left; //情况一cur为红p为红g为黑u存在且为红 if (uncle uncle-_col Red) { //变色 parent-_col uncle-_col Black; grandfather-_col Red; //继续往上处理 cur grandfather; parent cur-_parent; } //情况二情况三叔叔不存在或者叔叔存在且为黑 else { if (cur parent-_right)//p为g的右cur为p的右则进行左单旋 p变黑g变红 { // g // p // cur RotateL(grandfather); parent-_col Black; grandfather-_col Red; } else//p是g的右cur是p的左则进行右左双旋 cur变黑 g变红 { // g // p // cur RotateRL(grandfather); cur-_col Black; grandfather-_col Red; } break; } } } _root-_col Black;//暴力处理把根变成黑色 return true; } //1、左单旋 void RotateL(Node* parent) { Node* subR parent-_right; Node* subRL subR-_left; Node* ppNode parent-_parent;//提前保持parent的父亲 //1、建立parent和subRL之间的关系 parent-_right subRL; if (subRL)//防止subRL为空 { subRL-_parent parent; } //2、建立subR和parent之间的关系 subR-_left parent; parent-_parent subR; //3、建立ppNode和subR之间的关系 if (parent _root) { _root subR; _root-_parent nullptr; } else { if (parent ppNode-_left) { ppNode-_left subR; } else { ppNode-_right subR; } subR-_parent ppNode;//三叉链双向链接关系 } } //2、右单旋 void RotateR(Node* parent) { Node* subL parent-_left; Node* subLR subL-_right; Node* ppNode parent-_parent; //1、建立parent和subLR之间的关系 parent-_left subLR; if (subLR) { subLR-_parent parent; } //2、建立subL和parent之间的关系 subL-_right parent; parent-_parent subL; //3、建立ppNode和subL的关系 if (parent _root) { _root subL; _root-_parent nullptr; } else { if (parent ppNode-_left) { ppNode-_left subL; } else { ppNode-_right subL; } subL-_parent ppNode;//三叉链双向关系 } } //3、左右双旋 void RotateLR(Node* parent) { RotateL(parent-_left); RotateR(parent); } //4、右左双旋 void RotateRL(Node* parent) { RotateR(parent-_right); RotateL(parent); }6、红黑树的验证红黑树的验证主要分为两大步骤1、检测其是否满足二叉搜索树(中序遍历是否为有序序列)2、检测其是否满足红黑树的性质接下来分别演示1、检测其是否满足二叉搜索树(中序遍历是否为有序序列)这里只需要递归写一个中序遍历并判断测试用例的结果是否为一个有序序列即可判断二叉搜索树//验证是否为一颗搜索二叉树 void InOrder() { _InOrder(_root);//调用中序遍历子树 cout endl; } //中序遍历的子树 void _InOrder(Node* root) { if (root nullptr) return; _InOrder(root-_left); cout root-_kv.first ; _InOrder(root-_right); }2、检测其是否满足红黑树的性质这里只要判断是否满足红黑树的5大规则即可具体操作如下1、根节点是否为黑色2、任意一条路径黑色节点数是否相同递归每一条和确定的一条比较是否相同3、递归检测是否违反性质三从而出现连续的红节点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 (pCur-_col Black) blackCount; pCur pCur-_left; } // 检测是否满足红黑树的性质k用来记录路径中黑色节点的个数 size_t k 0; return _IsValidRBTree(pRoot, k, blackCount); } bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount) { //走到null之后判断k和black是否相等 if (pRoot nullptr) { if (k ! blackCount) { cout 违反性质四每条路径中黑色节点的个数必须相同 endl; return false; } return true; } // 统计黑色节点的个数 if (pRoot-_col Black) k; // 检测当前节点与其双亲是否都为红色 Node* pParent pRoot-_parent; if (pParent pParent-_col Red pRoot-_col Red) { cout 违反性质三没有连在一起的红色节点而这里出现了 endl; return false; } return _IsValidRBTree(pRoot-_left, k, blackCount) _IsValidRBTree(pRoot-_right, k, blackCount); }7、红黑树的删除红黑树的删除这里和AVL树一样就不做过多演示了具体可参考《算法导论》或者《STL源码剖析》8、红黑树与AVL树的比较红黑树和AVL树都是高效的平衡二叉树增删改查的时间复杂度都是O(logN)红黑树不追求绝对平衡其只需保证最长路径不超过最短路径的2倍相对而言降低了插入和旋转的次数所以在经常进行增删的结构中性能比AVL树更优而且红黑树实现比较简单所以实际运用中红黑树更多。9、红黑树的应用1、C STL库 -- map/set、mutil_map/mutil_set2、Java 库3、linux内核4、其他一些库
延伸阅读

更多相关文章

2026/10/16 2:27:41

专科生C语言实战解题逻辑:内存操作与输入鲁棒性

1. 这不是一份“标准答案”,而是一套专科生能真正吃透的C语言实战解题逻辑 你搜到这篇题解,大概率正处在两个状态之一:要么是刚打完国赛、手还在抖,对着屏幕反复核对最后一道题的边界条件;要么是下个月就要参赛&#x…

2026/10/17 13:52:43

STM32新入门系列解析:Cortex-M33与HAL库如何重塑嵌入式开发

意法半导体发布新STM32系列:当入门级微控制器开始不讲武德 做嵌入式开发这些年,我对ST的套路太熟了。每隔一段时间,ST就会丢出一颗新芯片,把上一代旗舰的部分能力下放到入门系列,然后整个行业跟着换平台、改板子、调代…

2026/10/16 4:50:13

数学建模实战:土壤重金属污染分析全流程解析与GIS空间插值应用

1. 项目背景与核心挑战2011年的全国大学生数学建模竞赛A题,题目是“城市表层土壤重金属污染分析”。这个题目,即使放在今天来看,也极具现实意义和挑战性。它不像一些纯理论推导题,而是把一个真实的环境科学问题,直接抛…

2026/10/17 14:25:31

LangChain 1.x多轮记忆实战:从上下文管理到摘要对话方案

1. 项目概述:为什么“有记忆”的对话这么难这阵子一直在啃 langchain 1.x,前面把基础链路的搭建跑通之后,紧接着就撞上了一个所有做对话应用都绕不开的坎:多轮记忆。说白了,你要做一个能真正聊起来的应用,不…

2026/10/17 14:25:31

鸿蒙Flutter下stx_bloc_base适配:BLoC状态机与事件时序排坑

1. 适配背景与整体思路写这篇东西之前,先说我为什么碰这个库。有段时间在做鸿蒙端 Flutter 应用的架构迁移,页面能跑,数据也在刷,但状态层一到复杂交互就出幺蛾子:快速切换 tab 会闪到旧状态、后台切回来偶尔读到过期数…

2026/10/17 14:25:31

线性代数工程实战:矩阵分解选型与数值稳定性避坑指南

简介:这份资源是《Linear Algebra with Applications》第十版全球版的电子书,作者为Steven J. Leon与Lisette de Pillis,面向高校数学、工程、计算机等专业修读线性代数课程的学生,以及需要复习或自学该学科的读者。全书共8章、55…

2026/10/17 14:25:31

Quectel GobiNet驱动V1.6.2.9编译安装与排错指南

简介:本资源是移远通信(Quectel)官方GobiNet驱动程序的Linux与Android双平台适配版本,专为嵌入式开发者、Linux系统工程师及物联网设备集成人员设计,用于在Linux内核或Android系统中驱动Gobi系列4G/LTE无线模块&#x…

2026/10/17 14:25:31

Python 3.8 入门教程完整版:从环境搭建到写出第一个实用工具

简介:这份PDF教程面向零基础编程学习者与希望系统梳理Python 3.8的开发者,围绕Windows与Linux双平台环境搭建、基础语法到面向对象编程的完整知识链路展开,帮助读者从入门逐步过渡到独立编写程序。资源包内共1个PDF文件,压缩包约1…

2026/10/17 14:20:30

2022国赛B题纯方位无源定位:从圆形到锥形编队的建模与MATLAB实现

简介:本资源为2022年全国大学生数学建模竞赛本科组B题的完整论文PDF,聚焦无人机纯方位无源定位问题,适合备战数模竞赛的本科生及指导教师参考。论文围绕圆形与锥形编队,建立被动接收信号无人机的定位模型与位置调整模型&#xff0…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/17 14:20:58

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑