【C++】map、set 的底层是什么?红黑树五条性质、插入旋转到模拟实现

C++ 红黑树详解(map、set 模拟实现)

文章目录

  • [C++ 红黑树详解(map、set 模拟实现)](#C++ 红黑树详解(map、set 模拟实现))
    • 一、红黑树的概念与性质
    • 二、红黑树的节点定义
    • 三、红黑树的插入:变色与旋转(核心)
      • [3.1 新节点为什么默认红色](#3.1 新节点为什么默认红色)
      • [3.2 插入流程总览](#3.2 插入流程总览)
      • [3.3 情况一:叔叔存在且为红,变色后继续向上](#3.3 情况一:叔叔存在且为红,变色后继续向上)
      • [3.4 情况二:叔叔不存在或为黑,单旋加变色](#3.4 情况二:叔叔不存在或为黑,单旋加变色)
      • [3.5 情况三:叔叔不存在或为黑,双旋加变色](#3.5 情况三:叔叔不存在或为黑,双旋加变色)
      • [3.6 旋转函数:三叉链下的单旋实现](#3.6 旋转函数:三叉链下的单旋实现)
      • [3.7 Insert 完整代码](#3.7 Insert 完整代码)
    • 四、红黑树的验证:IsBalance
    • [五、红黑树与 AVL 树的对比](#五、红黑树与 AVL 树的对比)
      • [5.1 概念对比](#5.1 概念对比)
    • 六、红黑树的迭代器
      • [6.1 迭代器的设计](#6.1 迭代器的设计)
      • [6.2 operator++:中序后继](#6.2 operator++:中序后继)
      • [6.3 operator--:中序前驱与 end 特例](#6.3 operator--:中序前驱与 end 特例)
      • [6.4 完整代码](#6.4 完整代码)
    • [七、用红黑树封装 set 与 map(模拟实现)](#七、用红黑树封装 set 与 map(模拟实现))
      • [7.1 一个树,两种容器:K、T、KeyOfT 三个模板参数](#7.1 一个树,两种容器:K、T、KeyOfT 三个模板参数)
      • [7.2 封装 set](#7.2 封装 set)
      • [7.3 封装 map 与 operator\[\]](#7.3 封装 map 与 operator[])
      • [7.4 测试结果](#7.4 测试结果)
    • 八、完整代码
    • 总结

上一篇笔记我们走完了关联式容器的使用层:set 是 K 模型的去重加排序容器,map 是 KV 模型的键值对容器, operator[] 借助 insert 的返回值实现了"不存在则插入、存在则查找"的复合功能;笔记结尾还铺垫了 AVL 树的概念,讲了平衡因子、更新规则和三叉链。这篇笔记要回答上一篇留下的问题:map、set 的底层到底长什么样。答案是 红黑树 ,一棵自平衡的二叉搜索树。AVL 树的四种旋转和完整实现已经在上一篇专题里讲完,本篇不再重复,只在对比环节用到它。本篇会从红黑树的五条性质讲起,重点拆解插入时的变色与旋转处理,再用对比测试把红黑树和 AVL 树的实测数据摆出来,然后实现红黑树的迭代器,最后用这棵树把 set 和 map 完整封装出来,完成模拟实现。学完这篇,关联式容器的底牌就彻底揭开了。


一、红黑树的概念与性质

普通二叉搜索树在极端情况下会退化成链,查找效率变成 O(n),不可控。AVL 树通过"左右子树高度差不超过 1"把树控制得很平衡,但这个标准太严格,插入和删除时为了维持平衡要做大量旋转 。红黑树换了一个思路:不追求高度差,而是给节点染上颜色,用颜色规则把"最长路径"和"最短路径"的比例限制住,平衡要求更宽松,旋转次数自然更少。

红黑树的性质 有五条,这是面试高频考点,要能默写:

  1. 每个节点不是红色就是黑色
  2. 根节点必须是黑色
  3. 每个空节点(NIL 叶子)视为黑色
  4. 红色节点的两个孩子都是黑色,也就是任何路径上不能出现连续两个红色节点
  5. 从任一节点到其每个后代空节点的所有路径上,黑色节点的数目相同

为什么这五条能保证平衡?关键在第 4、5 条的组合 。性质 5 保证所有路径的黑色节点数相同,设最短路径全黑、黑节点数为 h;性质 4 保证红节点不能相邻,所以最长路径上红黑交替,红节点最多和黑节点一样多。最长路径的长度最多是 2h,也就是说最长路径不超过最短路径的 2 倍 ,树的高度被限制在 O(log n) 量级。红黑树的平衡是一个"比例 "概念,而 AVL 树的"高度差不超过 1"是一个"差值"概念,后者严格得多。

放宽平衡标准换来的是更少的旋转:AVL 树删除节点可能一路回溯旋转,红黑树删除最多旋转三次。STL 的 map、set 底层选择红黑树,正是因为实际使用中插入删除远比纯查找频繁,旋转少的优势更大。
#mermaid-svg-eG9LpzLYReUU6r5B{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eG9LpzLYReUU6r5B .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eG9LpzLYReUU6r5B .error-icon{fill:#552222;}#mermaid-svg-eG9LpzLYReUU6r5B .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eG9LpzLYReUU6r5B .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eG9LpzLYReUU6r5B .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eG9LpzLYReUU6r5B .marker.cross{stroke:#333333;}#mermaid-svg-eG9LpzLYReUU6r5B svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eG9LpzLYReUU6r5B p{margin:0;}#mermaid-svg-eG9LpzLYReUU6r5B .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eG9LpzLYReUU6r5B .cluster-label text{fill:#333;}#mermaid-svg-eG9LpzLYReUU6r5B .cluster-label span{color:#333;}#mermaid-svg-eG9LpzLYReUU6r5B .cluster-label span p{background-color:transparent;}#mermaid-svg-eG9LpzLYReUU6r5B .label text,#mermaid-svg-eG9LpzLYReUU6r5B span{fill:#333;color:#333;}#mermaid-svg-eG9LpzLYReUU6r5B .node rect,#mermaid-svg-eG9LpzLYReUU6r5B .node circle,#mermaid-svg-eG9LpzLYReUU6r5B .node ellipse,#mermaid-svg-eG9LpzLYReUU6r5B .node polygon,#mermaid-svg-eG9LpzLYReUU6r5B .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eG9LpzLYReUU6r5B .rough-node .label text,#mermaid-svg-eG9LpzLYReUU6r5B .node .label text,#mermaid-svg-eG9LpzLYReUU6r5B .image-shape .label,#mermaid-svg-eG9LpzLYReUU6r5B .icon-shape .label{text-anchor:middle;}#mermaid-svg-eG9LpzLYReUU6r5B .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eG9LpzLYReUU6r5B .rough-node .label,#mermaid-svg-eG9LpzLYReUU6r5B .node .label,#mermaid-svg-eG9LpzLYReUU6r5B .image-shape .label,#mermaid-svg-eG9LpzLYReUU6r5B .icon-shape .label{text-align:center;}#mermaid-svg-eG9LpzLYReUU6r5B .node.clickable{cursor:pointer;}#mermaid-svg-eG9LpzLYReUU6r5B .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eG9LpzLYReUU6r5B .arrowheadPath{fill:#333333;}#mermaid-svg-eG9LpzLYReUU6r5B .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eG9LpzLYReUU6r5B .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eG9LpzLYReUU6r5B .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eG9LpzLYReUU6r5B .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eG9LpzLYReUU6r5B .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eG9LpzLYReUU6r5B .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eG9LpzLYReUU6r5B .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eG9LpzLYReUU6r5B .cluster text{fill:#333;}#mermaid-svg-eG9LpzLYReUU6r5B .cluster span{color:#333;}#mermaid-svg-eG9LpzLYReUU6r5B div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eG9LpzLYReUU6r5B .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eG9LpzLYReUU6r5B rect.text{fill:none;stroke-width:0;}#mermaid-svg-eG9LpzLYReUU6r5B .icon-shape,#mermaid-svg-eG9LpzLYReUU6r5B .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eG9LpzLYReUU6r5B .icon-shape p,#mermaid-svg-eG9LpzLYReUU6r5B .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eG9LpzLYReUU6r5B .icon-shape .label rect,#mermaid-svg-eG9LpzLYReUU6r5B .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eG9LpzLYReUU6r5B .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eG9LpzLYReUU6r5B .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eG9LpzLYReUU6r5B :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 二叉搜索树
退化成链

最坏 O(n)
AVL 树

高度差不超过 1

控制严格 旋转多
红黑树

最长路径不超过最短的 2 倍

控制宽松 旋转少
STL map / set 的底层


二、红黑树的节点定义

红黑树的节点在二叉搜索树节点的基础上多了两样东西:三叉链的 _parent 指针(AVL 树一节已经引入,红黑树同样需要向上找父亲、祖父、叔叔),以及颜色成员 _col

颜色用枚举表示:

cpp 复制代码
enum Colour
{
	RED,
	BLACK
};
cpp 复制代码
template<class T>
struct RBTreeNode
{
	T _data;                 // 节点存的数据,泛型 T:set 存 key,map 存 pair

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;  // 三叉链:向上找父亲、祖父、叔叔
	Colour _col;             // 节点颜色

	RBTreeNode(const T& data)
		: _data(data)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

注意节点存的是泛型 T,而不是写死的 key 或 value。这是为了一棵树同时服务 set 和 map 做的设计,第七节封装的时候会展开讲。


三、红黑树的插入:变色与旋转(核心)

3.1 新节点为什么默认红色

插入新节点时默认给它染成红色,这是一个深思熟虑的选择。

如果默认黑色,性质 5 必被破坏:新节点所在路径的黑色节点数比其他路径多 1。而性质 5 是全局约束,所有路径的黑节点数都要一致,修起来要动全树。

如果默认红色,只可能破坏性质 4 :当父亲也是红色时出现连续红色。连续红色是局部问题,只影响从新节点到祖父的一条链,修复代价小。

两害相权取其轻,新节点默认红色。破坏面最小,修复最便宜。

3.2 插入流程总览

插入分四步:

  1. 按搜索树规则找到空位置,挂上新节点,颜色红色。
  2. 看父亲颜色:父亲是黑色,性质都没违反,直接结束。
  3. 父亲是红色,出现连续红色,看叔叔(祖父的另一个孩子)颜色分三种情况处理。
  4. 无论中间怎么处理,最后强制根节点变黑。

三种情况的处理手段各不相同:

情况 条件 处理
情况一 叔叔存在且为红 变色:父亲、叔叔变黑,祖父变红;祖父当作新节点继续向上检查
情况二 叔叔不存在或为黑,新节点与父亲、祖父在同侧(LL 或 RR) 单旋:对祖父旋转,父亲变黑、祖父变红
情况三 叔叔不存在或为黑,新节点与父亲、祖父在异侧(LR 或 RL) 双旋:先旋转父亲再旋转祖父,新节点变黑、祖父变红

#mermaid-svg-plOyIq8wjWcDV7IG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-plOyIq8wjWcDV7IG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-plOyIq8wjWcDV7IG .error-icon{fill:#552222;}#mermaid-svg-plOyIq8wjWcDV7IG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-plOyIq8wjWcDV7IG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-plOyIq8wjWcDV7IG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-plOyIq8wjWcDV7IG .marker.cross{stroke:#333333;}#mermaid-svg-plOyIq8wjWcDV7IG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-plOyIq8wjWcDV7IG p{margin:0;}#mermaid-svg-plOyIq8wjWcDV7IG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-plOyIq8wjWcDV7IG .cluster-label text{fill:#333;}#mermaid-svg-plOyIq8wjWcDV7IG .cluster-label span{color:#333;}#mermaid-svg-plOyIq8wjWcDV7IG .cluster-label span p{background-color:transparent;}#mermaid-svg-plOyIq8wjWcDV7IG .label text,#mermaid-svg-plOyIq8wjWcDV7IG span{fill:#333;color:#333;}#mermaid-svg-plOyIq8wjWcDV7IG .node rect,#mermaid-svg-plOyIq8wjWcDV7IG .node circle,#mermaid-svg-plOyIq8wjWcDV7IG .node ellipse,#mermaid-svg-plOyIq8wjWcDV7IG .node polygon,#mermaid-svg-plOyIq8wjWcDV7IG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-plOyIq8wjWcDV7IG .rough-node .label text,#mermaid-svg-plOyIq8wjWcDV7IG .node .label text,#mermaid-svg-plOyIq8wjWcDV7IG .image-shape .label,#mermaid-svg-plOyIq8wjWcDV7IG .icon-shape .label{text-anchor:middle;}#mermaid-svg-plOyIq8wjWcDV7IG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-plOyIq8wjWcDV7IG .rough-node .label,#mermaid-svg-plOyIq8wjWcDV7IG .node .label,#mermaid-svg-plOyIq8wjWcDV7IG .image-shape .label,#mermaid-svg-plOyIq8wjWcDV7IG .icon-shape .label{text-align:center;}#mermaid-svg-plOyIq8wjWcDV7IG .node.clickable{cursor:pointer;}#mermaid-svg-plOyIq8wjWcDV7IG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-plOyIq8wjWcDV7IG .arrowheadPath{fill:#333333;}#mermaid-svg-plOyIq8wjWcDV7IG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-plOyIq8wjWcDV7IG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-plOyIq8wjWcDV7IG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-plOyIq8wjWcDV7IG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-plOyIq8wjWcDV7IG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-plOyIq8wjWcDV7IG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-plOyIq8wjWcDV7IG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-plOyIq8wjWcDV7IG .cluster text{fill:#333;}#mermaid-svg-plOyIq8wjWcDV7IG .cluster span{color:#333;}#mermaid-svg-plOyIq8wjWcDV7IG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-plOyIq8wjWcDV7IG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-plOyIq8wjWcDV7IG rect.text{fill:none;stroke-width:0;}#mermaid-svg-plOyIq8wjWcDV7IG .icon-shape,#mermaid-svg-plOyIq8wjWcDV7IG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-plOyIq8wjWcDV7IG .icon-shape p,#mermaid-svg-plOyIq8wjWcDV7IG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-plOyIq8wjWcDV7IG .icon-shape .label rect,#mermaid-svg-plOyIq8wjWcDV7IG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-plOyIq8wjWcDV7IG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-plOyIq8wjWcDV7IG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-plOyIq8wjWcDV7IG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 黑色
红色
存在且为红
不存在或为黑
按搜索树规则插入新节点

颜色默认为红
父亲是什么颜色
什么都不用做

结束
叔叔什么颜色
变色:父亲、叔叔变黑

祖父变红
祖父当作新节点

继续向上检查
旋转加变色
单旋:LL 右旋 / RR 左旋

双旋:LR 先左后右 / RL 先右后左
结束

LL、LR、RR、RL 的命名来自"祖父、父亲、新节点"三点的位置形状:LL 是父亲是祖父的左孩子、新节点是父亲的左孩子,即两次都在左侧;LR 是父亲在左、新节点在右,折线形状;RR、RL 是右侧的镜像。这个形状划分和 AVL 树的旋转完全一样,AVL 学过的形状在这里直接复用。

3.3 情况一:叔叔存在且为红,变色后继续向上

形状如下:祖父必为黑色(性质 4 保证红色节点的父亲是黑色,父亲是红色,祖父只能是黑色),父亲和叔叔都是红色。

处理:父亲、叔叔变黑,祖父变红 。这样做的巧妙之处在于黑色节点数守恒:父亲、叔叔由红变黑,这两条路径各加 1 个黑节点;祖父由黑变红,减 1 个黑节点,净变化为 0,性质 5 局部保持,只有祖父自己变成了红色。

祖父变红之后,连续红色的问题从"父亲、新节点"上移到了"祖父、祖父的父亲"之间:如果祖父的父亲也是红色,又出现了连续红色,需要继续处理。所以把祖父当作新节点 ,进入下一轮循环。最坏情况下这个循环一路爬到根,所以最后统一执行根节点强制变黑,保证性质 2。

3.4 情况二:叔叔不存在或为黑,单旋加变色

以 LL 型为例(父亲是祖父的左孩子,新节点是父亲的左孩子),叔叔不存在或为黑:

处理:对祖父做右单旋 ,然后父亲变黑、祖父变红

旋转后父亲顶替祖父成为这棵子树的根,父亲变黑,向上不再有连续红色问题,处理结束。染色也是守恒的:子树内黑节点数不变,且子树根是黑色,不影响更上层

RR 型完全镜像:父亲是祖父的右孩子、新节点是父亲的右孩子,对祖父做左单旋,同样父亲变黑、祖父变红。

3.5 情况三:叔叔不存在或为黑,双旋加变色

以 LR 型为例(父亲是祖父的左孩子,新节点是父亲的右孩子),形状是折线:

处理分两步:先对父亲左旋,把折线捋直成 LL 形状;再对祖父右旋。染色与单旋不同:单旋是父亲变黑,双旋是新节点变黑、祖父变红

RL 型镜像:先对父亲右旋,再对祖父左旋,同样新节点变黑、祖父变红。

为什么双旋后是新节点变黑 而不是父亲变黑?因为双旋后顶替祖父位置的是新节点,它是新的子树根,必须保证子树根为黑色,所以新节点变黑,父亲保持红色,祖父变红。

3.6 旋转函数:三叉链下的单旋实现

旋转只改指针,不改颜色,颜色由 Insert 里的分支决定。和 AVL 树的旋转形状完全一样,区别是 AVL 旋转后要更新平衡因子,红黑树没有平衡因子,反而更简单。

左单旋(以 parent 为轴,parent 的右孩子 subR 顶上来):

cpp 复制代码
void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	// 1. subRL 过继给 parent 当右孩子
	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	// 2. subR 顶替 parent 的位置
	Node* parentParent = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;

	// 3. 处理 subR 与上层的关系
	if (parentParent == nullptr)
	{
		_root = subR;              // parent 是根,subR 成为新根
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == parentParent->_left)
			parentParent->_left = subR;
		else
			parentParent->_right = subR;

		subR->_parent = parentParent;
	}
}

三叉链版本的旋转比二叉链麻烦一点:除了改动旋转点内部的三个指针,还要处理 subR 的父指针(更新 subRL 和 parent 的 _parent)、以及上层 parentParent 的指向(parent 是根则更新 _root,否则把 parentParent 对应方向的指针指向 subR)。每一步都要同步维护 _parent,漏掉一个三叉链就断了,后面向上查找祖先就会出错。

右单旋完全镜像:subL 是 parent 的左孩子,subLR 过继给 parent 当左孩子,subL 顶替 parent 位置,其余逻辑对称。

3.7 Insert 完整代码

cpp 复制代码
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef RBTreeIterator<T, T&, T*> Iterator;
	typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

	// 拷贝构造、赋值、析构与搜索树版本一致(深拷贝 Copy、后序销毁 Destroy),这里省略

	pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			// 空树:第一个节点作为根,必须是黑色
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(Iterator(_root, _root), true);
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))       // 键小,往右走
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))  // 键大,往左走
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(Iterator(cur, _root), false);  // 键已存在,插入失败
			}
		}

		// 挂上新节点,默认红色
		cur = new Node(data);
		cur->_col = RED;
		if (kot(parent->_data) < kot(data))
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		Node* newnode = cur;

		// 父亲是红色,出现连续红色,进入调整
		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)
					{
						// LL 型:对祖父右单旋,父亲变黑、祖父变红
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						// LR 型:先左旋父亲,再右旋祖父,新节点变黑、祖父变红
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else                                // 父亲是祖父的右孩子,镜像处理
			{
				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)
					{
						// RR 型:对祖父左单旋,父亲变黑、祖父变红
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						// RL 型:先右旋父亲,再左旋祖父,新节点变黑、祖父变红
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}

		// 情况一向上迭代时根可能被染红,最后强制变黑
		_root->_col = BLACK;

		return make_pair(Iterator(newnode, _root), true);
	}

Find 与普通搜索树一致,只是比较时要用 KeyOfT 取出键:

cpp 复制代码
	Iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key)
				cur = cur->_right;
			else if (kot(cur->_data) > key)
				cur = cur->_left;
			else
				return Iterator(cur, _root);
		}

		return End();
	}

四、红黑树的验证:IsBalance

插入逻辑分支多,写完后不能靠肉眼确认,需要一个验证函数检查树是否真的满足红黑树性质。验证思路很直接,对照性质逐条检查:

  1. 根节点不能是红色(性质 2)。
  2. 取一条参考路径(最左路径),数出上面的黑色节点个数 refNum(性质 5 的参考值)。
  3. 递归检查每个节点:不能出现连续红色(性质 4);每条路径走到空节点时,黑色节点个数必须和 refNum 相等(性质 5)。
cpp 复制代码
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;                // 性质 2:根必须是黑色

		// 参考值:最左路径上的黑色节点个数
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++refNum;
			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}

private:
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			// 走到空节点,这条路径的黑色节点数必须和参考值一致
			if (refNum != blackNum)
			{
				cout << "存在黑色节点数量不同的路径" << endl;
				return false;
			}
			return true;
		}

		// 性质 4:不能出现连续红色
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

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

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

几个实现细节。性质 3(空节点视为黑色)是约定,代码里"走到空节点"这个分支就代表遇到黑色空节点,不用显式处理。连续红色的检查写成"当前节点是红色且父亲是红色",因为循环里不能写 cur == RED 然后检查孩子,孩子可能为空,用父亲判空更安全:能走到这里的节点 _parent 一定不为空(根节点已在入口检查过是黑色,短路了后面的判断)。两条路径递归用与连接,任何一条路径不满足就整体返回 false。

插入若干数据后调用 IsBalance,返回 true 说明树的红黑性质完好,可以放心把插入逻辑交付。


五、红黑树与 AVL 树的对比

5.1 概念对比

对比维度 AVL 树 红黑树
平衡标准 左右子树高度差不超过 1 最长路径不超过最短路径的 2 倍
平衡强度 严格 宽松
树高 较高
插入旋转次数 最多 2 次(含双旋) 最多 2 次(含双旋)
删除旋转次数 最多 O(log n) 次回溯 最多 3 次
单次查找 略快(树更矮) 略慢
插入删除 旋转多,较慢 旋转少,较快
典型应用 对查找效率要求极高的场景 STL map/set、Linux 内核、Java TreeMap

对比的核心是平衡强度的取舍。AVL 用"高度差不超过 1"把树控制得很矮,查找路径短;红黑树只保证"最长不超过最短的 2 倍",树更高,但旋转少。删除上差距最明显:AVL 删除后要沿着祖先链回溯旋转,最坏 O(log n) 次;红黑树删除最多旋转 3 次。实际场景中插入删除比纯查找频繁,所以 STL 选择了红黑树。


六、红黑树的迭代器

6.1 迭代器的设计

红黑树的迭代器是一个自定义类,成员有两个:_node 指向当前节点,_root 指向树的根。

cpp 复制代码
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	Node* _node;   // 当前节点
	Node* _root;   // 根节点,--end() 时要用
	...

为什么除了 _node 还要存 _root?因为 end() 返回的迭代器_node是空指针,对 end()-- 操作要找到整棵树的中序最后一个节点 ,也就是最右节点,这个必须从根出发往下找,没有 _root 指针做不到。

模板参数 RefPtr 用来区分普通迭代器和 const 迭代器:普通迭代器是 RBTreeIterator<T, T&, T*>,解引用返回 T 的引用;const 迭代器是 RBTreeIterator<T, const T&, const T*>,解引用返回 const 引用。一个模板类同时实例化出两种迭代器,和 list 的模拟实现手法一致。

6.2 operator++:中序后继

迭代器往前走一步,要找中序遍历的下一个节点,分两种情况:

  1. 右子树不为空:下一个就是右子树最左的节点。
  2. 右子树为空:当前节点的中序后继是"沿父亲向上爬,直到自己是父亲左孩子"时的那个祖先。如果一路爬到空,说明当前是整棵树最后一个节点,++ 后变成 end()。
cpp 复制代码
	Self& operator++()
	{
		if (_node->_right)
		{
			// 右子树不为空:右子树最左节点就是中序下一个
			Node* leftMost = _node->_right;
			while (leftMost->_left)
				leftMost = leftMost->_left;
			_node = leftMost;
		}
		else
		{
			// 右子树为空:沿父亲向上爬,找到"孩子是父亲的左孩子"的情况
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}

		return *this;
	}

6.3 operator--:中序前驱与 end 特例

-- 是 ++ 的镜像,逻辑对称:

  1. _node 是空指针(end()):中序最后一个是整棵树最右节点。
  2. 左子树不为空:前驱是左子树最右的节点。
  3. 左子树为空:沿父亲向上爬,找到"孩子是父亲右孩子"的那个祖先。
cpp 复制代码
	Self& operator--()
	{
		if (_node == nullptr)   // end():中序最后一个是整棵树最右节点
		{
			Node* rightMost = _root;
			while (rightMost && rightMost->_right)
				rightMost = rightMost->_right;
			_node = rightMost;
		}
		else if (_node->_left)
		{
			// 左子树不为空:左子树最右节点就是中序上一个
			Node* rightMost = _node->_left;
			while (rightMost->_right)
				rightMost = rightMost->_right;
			_node = rightMost;
		}
		else
		{
			// 左子树为空:沿父亲向上爬,找到"孩子是父亲右孩子"的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}

		return *this;
	}

6.4 完整代码

剩下的运算符和 list 迭代器几乎一样:operator* 返回节点数据的引用,operator-> 返回节点数据的指针,operator== 和 operator!= 比较 _node 指针。遍历从 Begin 开始:Begin 返回最左节点,也就是中序第一个;End 返回空节点迭代器。所以用迭代器遍历红黑树,天然就是有序的。

cpp 复制代码
	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	bool operator==(const Self& s)
	{
		return _node == s._node;
	}
};

七、用红黑树封装 set 与 map(模拟实现)

7.1 一个树,两种容器:K、T、KeyOfT 三个模板参数

set 的节点存 key,map 的节点存 pair。如果红黑树的比较逻辑写死"比较 key",map 用不了;写死"比较 pair 的 first",set 用不了。代码用三个模板参数解决这个问题:

cpp 复制代码
template<class K, class T, class KeyOfT>
class RBTree

三个参数各司其职:K 是查找时用的键类型,T 是节点实际存的数据类型,KeyOfT 是一个仿函数 ,负责从 T 中取出 K。树内部比较时永远通过 KeyOfT 取键,不关心 T 具体长什么样。这正好用上仿函数学过的思想:比较规则做成对象传进容器

cpp 复制代码
// 插入时取键比较
KeyOfT kot;
if (kot(cur->_data) < kot(data))      // 键小,往右走
	cur = cur->_right;

// 查找时用 key 比较
if (kot(cur->_data) < key)
	cur = cur->_right;

7.2 封装 set

set 的数据类型 T 就是 key 本身,KeyOfT 直接返回 key。树实例化成 RBTree<K, const K, SetKeyOfT>:节点存 const K ,保证 key 一旦插入就不可修改(key 改了会破坏搜索树规则)。

cpp 复制代码
namespace bit
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}

		const_iterator begin() const
		{
			return _t.Begin();
		}

		const_iterator end() const
		{
			return _t.End();
		}

		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

	private:
		RBTree<K, const K, SetKeyOfT> _t;
	};
}

几个细节。迭代器直接复用红黑树的 Iterator 和 ConstIterator,用 typedef 重命名成 STL 习惯的小写 begin、end、iterator 风格。typedef 前面要加 typename (防止编译器将其视为变量):RBTree<K, const K, SetKeyOfT>::Iterator依赖类型,编译器在模板实例化前不知道它是类型还是成员,必须显式声明。insert 和 find 都是透传,返回值原样转发,insert 返回的 pair 里 bool 表示插入成功与否,和真实 set 的语义一致。

7.3 封装 map 与 operator\[\]

map 的数据类型是 pair,KeyOfT 从 pair 里取出 first。树实例化成 RBTree<K, pair<const K, V>, MapKeyOfT>first 是 const 的 (pair<const K, V>),key 不可修改;second 没有 const,value 可以改

cpp 复制代码
namespace bit
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;

		iterator begin()
		{
			return _t.Begin();
		}

		iterator end()
		{
			return _t.End();
		}

		const_iterator begin() const
		{
			return _t.Begin();
		}

		const_iterator end() const
		{
			return _t.End();
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}

		iterator find(const K& key)
		{
			return _t.Find(key);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

operator[] 的实现只有两行,完整复刻了笔记 16 讲过的原理:先 insert(make_pair(key, V())),key 不存在就插入缺省值,key 存在就插入失败;insert 无论如何都返回 pair,取 first 拿到迭代器,迭代器箭头解引用得到 kv,取 second 返回引用。调用方对返回值赋值或修改,就是在修改树里真实存储的 value。

这个模拟实现是核心骨架:真实 STL 的 map 还提供 erase、lower_bound、upper_bound、equal_range 等接口,但底层的树、迭代器、插入调整逻辑已经完全一致,把使用篇学过的接口按同样思路补全即可。

7.4 测试结果

set 的测试:插入一批乱序数据,正序和反序各遍历一遍。

cpp 复制代码
void test_set()
{
	set<int> s;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		s.insert(e);
	}

	for (auto e : s)          // 正序遍历
	{
		cout << e << " ";
	}
	cout << endl;

	// 反向遍历:从 end() 开始 --,走到 begin() 为止
	set<int>::iterator it = s.end();
	while (it != s.begin())
	{
		--it;
		cout << *it << " ";
	}
	cout << endl;
}

运行输出:

text 复制代码
1 2 3 4 5 6 7 14 15 16
16 15 14 7 6 5 4 3 2 1

中序遍历天然有序,去重加排序的效果和真实 set 一致。反向遍历从 end() 开始不断 --,正好依赖 6.3 节 --end() 的特例处理。

map 的测试:插入几个键值对,再用 operator\[\] 做修改和缺省插入。

cpp 复制代码
void test_map()
{
	map<string, string> dict;
	dict.insert({ "sort", "排序" });
	dict.insert({ "left", "左边" });
	dict.insert({ "right", "右边" });

	dict["left"] = "左边(已改)";    // key 已存在:修改 value
	dict["insert"] = "插入";        // key 不存在:插入 { "insert", "" } 再赋值
	dict["string"];                 // key 不存在:只插入缺省值空串

	map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		// it->first 是 const 的,不能修改 key;second 可以修改
		it->second += 'x';

		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}

运行输出(按 key 字典序):

text 复制代码
insert:插入x
left:左边(已改)x
right:右边x
sort:排序x
string:x

输出验证了三件事。第一,遍历按 key 字典序(insert 在 left 前,sort 在 string 前)。第二,dict["left"] 修改成功,验证 operator[] 的"存在则查找";dict["insert"] 插入成功,验证"不存在则插入"。第三,dict["string"] 只读不赋值,也往树里塞进了一个空串,输出里 string 只有追加的 x,这正是笔记 16 强调过的陷阱:operator\[\] 做纯查找会静默插入缺省值。模拟实现和真实 map 的行为完全一致。

八、完整代码

红黑树模拟实现

cpp 复制代码
#pragma once
#include <cassert>
#include <iostream>

enum Color
{
    RED,
    BLACK
};

template <class Data>
struct RBTreeNode
{
    Data _data;
    RBTreeNode<Data> *_left;
    RBTreeNode<Data> *_right;
    RBTreeNode<Data> *_parent; // 父节点
    Color _color;

    RBTreeNode()
        : _data(Data()), _left(nullptr), _right(nullptr), _parent(nullptr), _color(RED)
    {
    }

    RBTreeNode(const Data &data)
        : _data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _color(RED)
    {
    }
};

// 迭代器
template <class Data, class Ref, class Ptr>
struct RBTree_Iterator
{
    typedef RBTreeNode<Data> Node;
    typedef RBTreeNode<Data> *PNode;
    typedef RBTree_Iterator<Data, Ref, Ptr> Self;

    PNode _node;
    PNode _root; // 树的根节点,注意如果红黑树发生旋转,可能会导致迭代器失效

    RBTree_Iterator(PNode node, PNode root)
        : _node(node), _root(root)
    {
    }

    Ref operator*()
    {
        return _node->_data;
    }

    Ptr operator->()
    {
        return &_node->_data;
    }

    bool operator!=(const Self &s)
    {
        return s._node != _node;
    }

    bool operator==(const Self &s)
    {
        return s._node == _node;
    }

    Self &operator++()
    {
        // 整体思想为中序遍历
        if (_node && _node->_right)
        {
            // 如果当前节点的右子树存在,找右子树的最小值
            PNode MostSubL = _node->_right;
            while (MostSubL && MostSubL->_left)
            {
                MostSubL = MostSubL->_left;
            }
            _node = MostSubL;
        }
        else
        {
            // 如果当前节点的右子树不存在,代表当前子树已经遍历完成,需要查找父节点
            // 此外,如果当前节点是父节点的右节点,代表以父节点的子树已经遍历过了,需要查找父节点的父节点,如此循环
            PNode cur = _node;
            PNode parent = _node->_parent;
            while (parent && cur == parent->_right)
            {
                cur = parent;
                parent = cur->_parent;
            }
            // 循环出来之后,要么 parent 为空,要么 parent 还有右子树没有遍历,此时parent就是目标节点
            _node = parent;
        }

        return *this;
    }

    Self &operator--()
    {
        if (_node == nullptr)
        {
            // _node为空,我们将他视为end(),返回当前树的最右节点
            if (_root == nullptr) return *this;   // 空树,end() 的前驱还是 end()
            PNode MostSubR = _root;               // 从根出发找最右节点(根可能没有右孩子)
            while (MostSubR->_right)
            {
                MostSubR = MostSubR->_right;
            }
            _node = MostSubR;
        }
        else if (_node->_left)
        {
            // 如果当前节点的左子树存在,找左子树的最大值
            PNode MostSubR = _node->_left;
            while (MostSubR->_right)
            {
                MostSubR = MostSubR->_right;
            }
            _node = MostSubR;
        }
        else
        {
            // 如果当前节点的左子树不存在,代表当前子树已经遍历完成,需要查找父节点
            // 此外,如果当前节点是父节点的右节点,代表以父节点的子树已经遍历过了,需要查找父节点的父节点,如此循环
            PNode cur = _node;
            PNode parent = _node->_parent;
            while (parent && cur == parent->_left)
            {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;
        }

        return *this;
    }
};

template <class K, class Data, class KofData>
class RBTree
{
    typedef RBTreeNode<Data> Node;
    typedef RBTreeNode<Data> *PNode;

private:
    PNode _root = nullptr; // 必须初始化,否则默认构造出的 _root 是野指针

    // 先序遍历
    void _InOrder(PNode n)
    {
        if (n == nullptr)
            return;

        _InOrder(n->_left);
        std::cout << " data: " << n->_data << std::endl;
        _InOrder(n->_right);
    }

    // 拷贝子节点
    PNode copy(PNode node)
    {
        if (node == nullptr)
            return nullptr;

        // 先序遍历创建节点
        PNode newNode = new Node(node->_data);
        PNode left = copy(node->_left);
        PNode right = copy(node->_right);

        newNode->_left = left;
        newNode->_right = right;
        newNode->_color = node->_color;

        if (left)
            left->_parent = newNode;
        if (right)
            right->_parent = newNode;

        return newNode;
    }

    // 销毁子节点
    void Destory(PNode node)
    {
        if (node == nullptr)
            return;

        // 后序遍历
        Destory(node->_left);
        Destory(node->_right);
        delete node;
    }

    // 左单旋:
    void RotateL(PNode parent)
    {
        PNode subR = parent->_right;
        PNode subRL = subR->_left;
        PNode PP = parent->_parent;

        // 从subRL出发
        if (subRL != nullptr) // 一定要注意 subRL 可能为空
            subRL->_parent = parent;

        // 从parent节点出发
        parent->_right = subRL;
        parent->_parent = subR;

        // 从subR出发
        subR->_left = parent;
        subR->_parent = PP;

        // 如果进行左旋的节点不是根节点(),需要改变 PP 的指向
        if (PP == nullptr) // 当前节点为根节点
            _root = subR;
        else
        {
            if (PP->_left == parent)
                PP->_left = subR;
            else if (PP->_right == parent)
                PP->_right = subR;
        }
    }

    // 右单旋:
    void RotateR(PNode parent)
    {
        PNode subL = parent->_left;
        PNode subLR = subL->_right;
        PNode PP = parent->_parent;

        // 从 subLR 出发
        if (subLR != nullptr)
            subLR->_parent = parent;

        // 从 parent 出发
        parent->_left = subLR;
        parent->_parent = subL;

        // 从 subL 出发
        subL->_right = parent;
        subL->_parent = PP;

        // 如果进行右旋的节点不是根节点(),需要改变 PP 的指向
        if (PP == nullptr)
            _root = subL;
        else
        {
            if (PP->_left == parent)
                PP->_left = subL;
            else
                PP->_right = subL;
        }
    }

public:
    // 迭代器
    typedef RBTree_Iterator<Data, Data &, Data *> iterator;
    typedef RBTree_Iterator<Data, const Data &, const Data *> const_iterator;

    // 注意不能返回引用
    iterator begin()
    {
        // 找搜索树中最左侧的节点
        PNode MostSubR = _root;
        while (MostSubR && MostSubR->_left)
        {
            MostSubR = MostSubR->_left;
        }
        return iterator(MostSubR, _root);
    }

    iterator end()
    {
        return iterator(nullptr, _root);
    }

    const_iterator cbegin()
    {
        // 找搜索树中最左侧的节点
        PNode MostSubR = _root;
        while (MostSubR && MostSubR->_left)
        {
            MostSubR = MostSubR->_left;
        }
        return const_iterator(MostSubR, _root);
    }

    const_iterator cend()
    {
        return const_iterator(nullptr, _root);
    }

public:
    RBTree()
        : _root(nullptr)
    {
    }

    RBTree(const RBTree &root)
    {
        _root = copy(root._root);
    }

    RBTree &operator=(const RBTree &root)
    {
        if (this != &root) // 防止自赋值
        {
            Destory(_root);
            _root = copy(root._root);
        }
        return *this;
    }

    ~RBTree()
    {
        Destory(_root);
        _root = nullptr;
    }

    std::pair<iterator, bool> Insert(const Data &data)
    {
        // 如果根节点为空,则为根节点
        if (_root == nullptr)
        {
            _root = new Node(data);
            _root->_color = BLACK;
            return std::make_pair(iterator(_root, _root), true);
            // return {{_root, _root}, true};
        }

        KofData kov;            // KofData 是类型名,KofData(x) 会被解析成构造一个临时对象,需要先定义,再使用(易错)
        PNode parent = nullptr; // 新节点的父节点
        PNode cur = _root;
        while (cur)
        {
            if (kov(cur->_data) < kov(data)) // 如果类型 T 为复杂对象,则需要重载运算符
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (kov(cur->_data) > kov(data))
            {
                parent = cur;
                cur = cur->_left;
            }
            else
                return std::make_pair(iterator(cur, _root), false); // 去重处理,不会插入重复内容
                                                                    // return {{cur, _root}, false};
        }

        cur = new Node(data); // cur变量的任务已经完成,这里复用了这个变量来存储新节点的值
        cur->_color = RED;
        PNode newnode = cur;

        if (kov(parent->_data) < kov(data))
            parent->_right = cur;
        else
            parent->_left = cur;
        cur->_parent = parent; // 一定要记得更新_parent的指向,这容易遗漏

        while (parent && parent->_color == RED)
        {
            PNode grand = parent->_parent; // parent 为红, 则一定有grand节点,因为红节点不能为根节点(规则1)
            PNode uncle = nullptr;
            if (parent == grand->_left)
                uncle = grand->_right;
            else
                uncle = grand->_left;

            if (uncle && uncle->_color == RED)
            {
                // uncle 存在且为红
                parent->_color = uncle->_color = BLACK;
                grand->_color = RED;

                cur = grand;
                parent = cur->_parent;
            }
            else
            {
                // uncle 不存在/存在且为黑
                if (parent == grand->_left)
                {
                    // 如果 parent 是 grand 的左子树
                    //     g
                    //   p   u
                    if (cur == parent->_left)
                    {
                        // 如果 cur 是 parent 的左子树
                        //     g
                        //   p   u
                        // c
                        // 右单旋
                        RotateR(grand);
                        //     p
                        //   c   g
                        //         u
                        parent->_color = BLACK;
                        grand->_color = RED;
                        // 旋转完成之后不需要再向上遍
                        // 因为此时 p 是当前子树的根节点,而p为黑,p的父节点不管是红/黑都不会违反规则
                        break;
                    }
                    else
                    {
                        // 如果 cur 是 parent 的右子树
                        //     g
                        //   p   u
                        //     c
                        // 对parent先左单旋,再对grand右单旋
                        RotateL(parent);
                        //     g
                        //   p   u
                        // c
                        RotateR(grand);
                        //     p
                        //   c   g
                        //         u
                        cur->_color = BLACK;
                        grand->_color = RED;
                        // 旋转完成之后不需要再向上遍
                        // 因为此时 p 是当前子树的根节点,而p为黑,p的父节点不管是红/黑都不会违反规则
                        break;
                    }
                }
                else
                {
                    // 如果 parent 是 grand 的左子树
                    //     g
                    //   u   p
                    if (cur == parent->_left)
                    {
                        // 如果 cur 是 parent 的左子树
                        //     g
                        //   u   p
                        //     c
                        // 对parent先右单旋,再对grand左单旋
                        RotateR(parent);
                        //     g
                        //   u   p
                        //         c
                        RotateL(grand);
                        //     p
                        //   g   c
                        // u
                        cur->_color = BLACK;
                        grand->_color = RED;
                        // 旋转完成之后不需要再向上遍
                        // 因为此时 p 是当前子树的根节点,而p为黑,p的父节点不管是红/黑都不会违反规则
                        break;
                    }
                    else
                    {
                        // 如果 cur 是 parent 的右子树
                        //     g
                        //   u   p
                        //         c
                        // 左单旋
                        RotateL(grand);
                        //     p
                        //   g   c
                        // u
                        parent->_color = BLACK;
                        grand->_color = RED;
                        // 旋转完成之后不需要再向上遍
                        // 因为此时 p 是当前子树的根节点,而p为黑,p的父节点不管是红/黑都不会违反规则
                        break;
                    }
                }
            }
        }

        _root->_color = BLACK; // 若 grand 是根且被染红,这里强制根为黑(性质1)
        return std::make_pair(iterator(newnode, _root), true);
    }

    void InOrder()
    {
        _InOrder(_root);
    }
};

set模拟封装

cpp 复制代码
#pragma once
#include "RBTree.hpp"

namespace myset
{
    template<class K>
    class set
    {
        struct SetKofV
        {
            const K& operator()(const K& key)
            {
                return key;
            }
        };
    private:
        RBTree<K, const K, SetKofV> _t;// 注意这里也要改成 const
    public:
        // 迭代器
        // 注意:无论是否为const迭代器,set都不支持使用迭代器改变节点的值,防止破坏红黑树的结构
        // 因此,我们传入 RBTree 模板的 Data 类型设置为 const 类型,防止修改
        typedef typename RBTree<K, const K, SetKofV>::iterator iterator;
        typedef typename RBTree<K, const K, SetKofV>::const_iterator const_iterator;

        iterator begin()
        {
            return _t.begin();
        }

        iterator end()
        {
            return _t.end();
        }

        const_iterator cbegin()
        {
            return _t.cbegin();
        }

        const_iterator cend()
        {
            return _t.cend();
        }

    public:
        std::pair<iterator, bool> insert(const K& key)
        {
            return _t.Insert(key);
        }
    };
}

map模拟封装

cpp 复制代码
#pragma once
#include "RBTree.hpp"

namespace mymap
{
    template<class K, class V>
    class map
    {
        struct MapKofV
        {
            const K& operator()(const std::pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    private:
        // 需要注意,map的val并不是模板中的 V 而是 pair<K, V>
        RBTree<K, std::pair<const K, V>, MapKofV> _t; // 注意这里也要改成 const

    public:
        // 迭代器
        // 注意:与set不同,map支持 val 的修改,不支持 key 的修改
        // 因此,我们传入 RBTree 模板的 Data 类型设置为 std::pair<const K, V> 类型,防止key被修改
        typedef typename RBTree<K, std::pair<const K, V>, MapKofV>::iterator iterator;
        typedef typename RBTree<K, std::pair<const K, V>, MapKofV>::const_iterator const_iterator;

        iterator begin()
        {
            return _t.begin();
        }

        iterator end()
        {
            return _t.end();
        }

        const_iterator cbegin()
        {
            return _t.cbegin();
        }

        const_iterator cend()
        {
            return _t.cend();
        }

        V& operator[](const K& key)
        {
            std::pair<iterator, bool> p = insert(key, V());
            return p.first->second;// 这里翻译一下: p.first 得到 iterator; iterator-> 得到 RBTree 的 Dataj 即 pair<K, V>, 之后 second 取出 V
        }

    public:
        std::pair<iterator, bool> insert(const K& key, const V& val)
        {
            return _t.Insert(std::pair<K, V>(key, val));
        }
    };
}

总结

这篇笔记揭开了 map、set 的底层:一棵红黑树。红黑树用五条性质约束平衡,核心是"无连续红色"和"所有路径黑色节点数相同",两条合起来保证最长路径不超过最短路径的 2 倍,高度稳定在 O(log n)。插入是整棵树的灵魂:新节点默认红色,把破坏面控制在连续红色这一条性质上;父亲是黑色直接结束,父亲是红色看叔叔------叔叔红就变色并向上迭代,叔叔黑或不存在就旋转,LL 右单旋、RR 左单旋、LR 先左后右、RL 先右后左,旋转后单旋染父亲、双旋染新节点,最后强制根变黑。验证函数对照性质逐条检查,最左路径的黑节点数作为参考值,递归确认每条路径黑节点数一致且无连续红。实测对比里,升序插入 1000 万节点,AVL 树更矮(24 对 44)插入更快(706 对 976 毫秒),旋转次数几乎相同,查找时间持平,说明"红黑树旋转少"的优势要放在更一般的随机场景里看。迭代器用 _node_root 两个指针实现中序前后驱,--end() 靠根指针找到最右节点。最后用 K、T、KeyOfT 三个模板参数让一棵树同时服务 set 和 map:set 存 const K,map 存 pair(const K, V),KeyOfT 负责取键,map 的 operator[] 两行代码复刻了"不存在则插入、存在则查找"。至此关联式容器的使用和底层全部贯通:会用容器,也看得懂容器的内脏,这就是 STL 学习的完整闭环。

相关推荐
汉克老师1 小时前
CSP-J 2026 初赛试题解析(第三部分:完善程序题(第二题))精讲
c++·csp-j·小学生·学c++编程
kukubuzai1 小时前
双指针系列二--末尾篇(3道题)
c++·算法·leetcode
君顾11 小时前
西安24小时自助健身房系统软件开发实战:需求分析与技术落地指南
java·开发语言·健身房
传奇开心果编程1 小时前
【Rust入门练中学】第4课:函数与所有权入门
开发语言·学习·rust
动词ing1 小时前
【题目练习】动态规划+背包问题
数据结构·目标检测
杜 硕1 小时前
单链表经典算法题
数据结构·算法
汉克老师2 小时前
CSP-J 2026 初赛试题解析(第二部分:阅读程序题(第二题))精讲
c++·csp-j·小学生·学c++编程