Appearance
红黑树
教材出处:大纲单列了这一条,但四本主参都没有单列这一节(严蔚敏全书「红黑树」零命中)。成文出处见 殷人昆《数据结构(用面向对象方法与 C++ 描述)》第 2 版 §7.5 红黑树:「红黑树(red-black tree)是这样的一棵二叉搜索树:树中的每一个结点的颜色不是黑色就是红色。可以把一棵红黑树视为一棵扩充二叉树,用外部结点表示空指针。」
为什么在 AVL 之外还要一个红黑树
AVL 树已经把树高限制在
对"查得多、改得少"的场景 AVL 很划算。但对插入删除极其频繁的场景——进程调度队列、内存管理的空闲块表、通用有序容器——每次删除都可能触发连锁旋转,代价就显出来了。
红黑树的做法是放松平衡条件:不要求左右子树高度严格相差不超过 1,只要求某种意义上的"黑色结点数相等"。代价是树可能比 AVL 高出将近一倍,换来的是插入最多转 2 次、删除最多转 3 次。
一句话概括这场交易:红黑树用"查找时多走几层"换"修改时少转几次"。 判断该选谁,就看这两件事在具体场景里哪件更频繁。
五条性质
红黑树是一棵二叉排序树,并且同时满足以下五条:
| 编号 | 性质 | 说明 |
|---|---|---|
| ① | 每个结点是红色或黑色 | 只需 1 bit 额外空间 |
| ② | 根结点是黑色 | — |
| ③ | 叶结点(NIL)是黑色 | 这里的"叶结点"指外部的空结点,不是没有孩子的内部结点 |
| ④ | 红结点的两个孩子都是黑色 | 等价于"不存在两个连续的红结点",简称不红红 |
| ⑤ | 从任一结点到其所有叶结点(NIL)的路径上,黑色结点数目相同 | 简称黑高相等 |
性质 ③ 里的"叶结点"是 NIL 空结点,不是普通的叶子。画树时一定要把 NIL 画出来,否则数黑高必然出错——性质 ⑤ 数的路径终点正是这些 NIL。
13(黑)
/ \
8(红) 17(红)
/ \ / \
NIL NIL NIL NIL ← 这些才是性质 ③ 说的"叶结点",全部为黑黑高
c
typedef enum { RED, BLACK } Color;
typedef struct RBNode {
int key;
Color color;
struct RBNode *left, *right, *parent; // 调整要向上回溯,所以存双亲指针
} RBNode;性质 ⑤ 不是"左右子树高度相同"。 红黑树完全允许左右子树的实际高度相差很多——只要黑结点数相同、红结点分布不同即可。把它读成"高度平衡"是对红黑树最典型的误解。
先看一眼
判定:机械三步
① 根是不是黑色;② 扫一遍红结点,看它的两个孩子(含 NIL)是否都黑;③ 自底向上算黑高——每个结点比较左右子树的黑高,不相等立刻判否,相等则把该结点黑高记为「孩子黑高
拿一棵树跑一遍:
13(黑)
/ \
8(红) 17(红)
/ \ / \
1(黑) 11(黑) 15(黑) 25(黑)
\ / \
6(红) 22(红) 27(红)第一步:根 13 是黑 ✓ 第二步:红结点有 8、17、6、22、27。8 的孩子 1、11 都是黑 ✓;17 的孩子 15、25 都是黑 ✓;6、22、27 的孩子都是 NIL(黑)✓ 第三步:自底向上算黑高(NIL 黑高为 0):
| 结点 | 颜色 | 左子树黑高 | 右子树黑高 | 是否相等 | 该结点黑高 |
|---|---|---|---|---|---|
| 6 | 红 | NIL → | NIL → | ✓ | 1 |
| 1 | 黑 | NIL → 1 | 6 是红 → | ✓ | 1 |
| 11 | 黑 | 1 | 1 | ✓ | 1 |
| 8 | 红 | 1 是黑 → | 11 是黑 → | ✓ | 2 |
| 15 | 黑 | 1 | 1 | ✓ | 1 |
| 25 | 黑 | 22 是红 → | 27 是红 → | ✓ | 1 |
| 17 | 红 | 15 是黑 → | 25 是黑 → | ✓ | 2 |
| 13 | 黑 | 8 是红 → | 17 是红 → | ✓ | 2 |
三步全过 → 是红黑树 ✓
再验证"最长
三种典型的"不是红黑树":根是红色(违反 ②);某红结点的孩子仍是红色(违反 ④);左子树黑高 2、右子树黑高 3(违反 ⑤)。
两条上界:树高为什么是
结论一:最长路径
这条结论说明红黑树"歪"是有限度的——虽然不像 AVL 那样左右几乎等高,但两侧的差距永远锁在 2 倍以内。
结论二:含
先证一个引理:以
主证明:设树高为
含义:红黑树的高度是
插入:新结点染红,然后看叔结点
按 BST 规则挂上新结点 → 染红 → 只有双亲也是红色(违反性质 ④)才需要调整;双亲是黑色就什么都不用做。
为什么必须染红:染黑会让该路径黑高
设当前结点
| 情形 | 条件 | 操作 | 结果 |
|---|---|---|---|
| 一 | 不旋转;令 | ||
| 二 | 对 | 转成直线,进入情形三 | |
| 三 |
为什么参照物是叔结点:修复思路是把
情形一:叔结点为红 —— 纯变色,向上迭代
G(黑) G(红) ← 新的 z,继续向上检查
/ \ 变色 / \
P(红) U(红) ──→ P(黑) U(黑)
/ /
z(红) z(红)为什么不破坏性质 ⑤:
情形二:叔结点为黑,
G(黑) G(黑)
/ \ 对 P 左旋 / \
P(红) U(黑) ──→ z(红) U(黑)
\ /
z(红) P(红) ← 交换角色后,P 与 z 成为一条直线情形三:叔结点为黑,
旋转次数:情形一不旋转;情形二旋转 1 次后必进入情形三;情形三旋转 1 次后终止——插入最多旋转 2 次,其余是
情形二、三图里的 到底是什么(觉得" 为黑"不自然时展开)
图里省略了各结点下方挂的子树。
就是刚插入的叶结点时, 只能是 NIL(NIL 视为黑)。因为此时 是红的、两个孩子原本都是 NIL, 这一侧的黑高只有 1; 若是实体黑结点,它自己就贡献 1 个黑、下面还有 NIL,黑高至少 2,性质 ⑤ 当场就不成立了。 是情形一变色上溯上来的内部结点时, 才可能是有实体的黑结点。
两种来源的旋转与变色步骤完全一样,所以正文统一按一张图讲。
删除:看兄弟,消化"双重黑"
先按 BST 规则删除;两个孩子时转化为删除中序后继,故实际被移除的结点最多只有一个孩子(详见 BST 的删除)。再看它的颜色:
- 移除的是红色结点:不计入黑高、删掉也不产生红红,五条性质全部保持,直接结束;
- 移除的是黑色结点:该位置到叶子的所有路径黑高减 1,破坏性质 ⑤,必须调整。
调整时把缺失的那一重黑色记账在顶替该位置的结点
| 情形 | 条件 | 操作 | 结果 |
|---|---|---|---|
| 1 | 新兄弟必为黑,转入情形 2 / 3 / 4 | ||
| 2 | |||
| 3 | 近侄染黑、 | 新兄弟的远侄变红,转入情形 4 | |
| 4 | 双重黑被消化,一步终结 |
为什么删除看兄弟、插入看叔结点:插入是"这一侧多了个红",要找同层另一半(叔)一起变色;删除是"这一侧少了个黑",只能从兄弟那侧借。参照物由"缺什么、向谁要"决定。
四种情形各自的道理:
情形 1 不解决问题,只是把"兄弟是红的"这个碍事的情况消掉——旋转后
情形 2 是唯一会向上迭代的:
情形 3 把"近侄红"转成"远侄红",为情形 4 铺路。
情形 4 一步终结:左旋后
"近侄 / 远侄"的定义:以
相对于兄弟 的方位来说,离 近的那个侄子叫近侄、远的叫远侄。 是左孩子时, 的左孩子是近侄、右孩子是远侄; 是右孩子时反过来。这个方位关系在镜像情形下会左右互换,是最容易搞反的一处。
旋转次数:情形 2 不旋转(只变色
删除四种情形的示意图(对照图逐步理解时展开)
情形 1:
P(黑) W(黑)
/ \ → / \
x(双黑) W(红) P(红) Wr
/ \ / \
Wl Wr x(双黑) Wl情形 2:
P(任意色) P(双黑或直接染黑)
/ \ → / \
x(双黑) W(黑) x(普通黑) W(红)
/ \ / \
Wl(黑) Wr(黑) Wl(黑) Wr(黑)情形 3:
P P
/ \ / \
x W(黑) → x Wl(黑)
/ \ \
Wl(红) Wr(黑) W(红)
\
Wr(黑)情形 4:
P(任意色) W(取原 P 的颜色)
/ \ → / \
x(双黑) W(黑) P(黑) Wr(黑)
/ \ / \
Wl Wr(红) x(普通黑) Wl四种情形的转化关系:
情形 1 ──→ 情形 2 / 3 / 4 (消掉"兄弟是红的")
情形 3 ──→ 情形 4 (把近侄红转成远侄红)
情形 4 ──────────→ 结束 (一步终结)
情形 2 ──→ 上移,最多 O log n 次迭代与 AVL 的对比,以及与 2-3-4 树的等价
| 对比项 | 红黑树 | AVL 树 |
|---|---|---|
| 平衡条件 | 黑高相等(弱平衡) | 左右子树高度差 |
| 最大高度 | ||
| 插入的旋转次数 | ||
| 删除的旋转次数 | ||
| 每结点额外空间 | 1 bit 颜色 | 一个高度(或 2 bit 平衡因子) |
| 适用场景 | 插入删除频繁的通用有序容器 | 查找密集、修改较少 |
一句话:查得多用 AVL,改得多用红黑树。 两者三个操作都是
还有一条对照值得知道:红黑树可以等价地转化为一棵 2-3-4 树(即 4 阶 B 树),做法是把每个红结点与它的黑色双亲"合并"成同一个 B 树结点——黑结点带 0 / 1 / 2 个红孩子分别对应 B 树的 2 / 3 / 4 结点。
性质 ⑤(黑高相等)在 B 树那边就是"所有叶结点在同一层";性质 ④(不红红)保证了合并后每个 B 树结点最多 3 个关键字。 理解了这个对应,红黑树那些看似古怪的调整规则,本质上就是 B 树的结点分裂与合并。
最后提一个容易被忽略的设计意图:旋转次数是常数、变色次数是
考点速记
三条会被反复调用的结论:
- 红黑树的平衡保证来自"不红红"(④)与"黑高相等"(⑤)的合力,由它们直接推出"最长
2 倍最短"与 。 - 新结点必须染红:染黑必定破坏全局的性质 ⑤,染红最多破坏局部的性质 ④。
- 旋转次数有常数上界(插入
、删除 ),这是它相对 AVL 的唯一实质优势。
这一节至今在 408 真题里不单独成题。 红黑树是较晚才写进大纲的条目(六(五)3),到目前为止还没有出现过直接考它的题——所以下方「真题练习」是空的,这不是漏挂。
不过"没考过"不等于"不用看",两件事值得留意:
- 它是大纲内条目,随时可能出选择题。 一旦出,最省事的考法就是判定题(给一棵着色的树问是不是红黑树)和性质辨析题(哪条叙述正确)。前者按上面那机械三步走;后者的坑集中在两处——性质 ③ 的"叶结点"指 NIL 不是普通叶子,性质 ⑤ 说的是黑结点数相同、不是高度相同。
- 与 AVL 的对比是更可能被考到的角度。 两者的量级都是
,能拉开差距的只有"删除的旋转次数:红黑树是常数、AVL 是 "这一条。
复习优先级上,红黑树应排在 BST 与 AVL 之后——那两节年年有题,本节暂时没有。
易错:把性质 ⑤ 读成"左右子树高度相同"。 红黑树允许左右实际高度相差近一倍。
易错:数黑高时不画 NIL。 性质 ⑤ 的路径终点正是这些 NIL,不画必然数错。
易错:把插入的参照物记成兄弟、删除的记成叔结点。 插入看叔、删除看兄。
教材出处
- 红黑树建立在二叉排序树之上,其定义(三条性质与"中序遍历得到递增有序序列"):严蔚敏《数据结构(C 语言版)》(第 2 版)印刷 p198(7.3.1 节)
- 引入平衡机制的动机("二叉排序树查找算法的性能取决于二叉树的结构……如果数据呈有序排列,则二叉排序树是线性的,查找的时间复杂度为
……事实上,树的高度越小,查找速度越快"):印刷 p205(7.3.2 节) - 4 阶 B 树(2-3-4 树)的定义,用于理解本篇的等价关系:印刷 p210(7.3.3 节)
说明:严蔚敏这本教材没有红黑树章节(红黑树是较晚才进入大纲的内容),因此本篇的五条性质、黑高、两条上界的证明、插入的三种情形与删除的四种情形,均为本文依据红黑树的公认定义自行整理与推导,不标教材页码。凡是本篇给出证明的地方,读者都可以按证明自行复核,不必依赖出处。
相关知识
二叉排序树(BST)(插入删除第一步都是 BST 操作)|平衡二叉树(AVL)(左旋 / 右旋即其 RR / LL 旋转)|B 树(与 4 阶 B 树等价)|散列表(拉链法)|查找算法的分析与应用