Appearance
第 4 讲 选择问题、搜索树、红黑树与跳表
本讲知识图谱
图表渲染中…
4.1 顺序统计量与选择问题
第
:最小值。 :最大值。 :中位数的一种定义。
如果先排序,再取第
最小值只需
4.2 RandomizedSelect
快速选择使用快速排序中的 PARTITION。Partition 后 pivot 已处在最终排名位置,只需递归进入包含目标排名的一侧。
text
RANDOMIZED-SELECT(A, p, r, i):
if p == r:
return A[p]
q = RANDOMIZED-PARTITION(A, p, r)
k = q - p + 1
if i == k:
return A[q]
else if i < k:
return RANDOMIZED-SELECT(A, p, q-1, i)
else:
return RANDOMIZED-SELECT(A, q+1, r, i-k)与快速排序不同,快速选择每层只递归一个子问题。最坏情况下每次只排除一个元素:
随机 pivot 下期望时间为
LeetCode 215 求第
4.3 最坏线性时间选择
最坏线性选择也叫 median of medians。
算法步骤:
- 把
个元素分成每组 5 个。 - 每组内部排序,取中位数。
- 递归地在这些中位数中找中位数
。 - 用
作为 pivot 做 partition。 - 只递归进入目标所在一侧。
关键性质:以
递推式为:
该递推解为
4.4 动态集合
动态集合维护一组随时间变化的元素。常见操作:
| 操作 | 含义 |
|---|---|
SEARCH(S, k) | 找关键字为 |
INSERT(S, x) | 插入元素 |
DELETE(S, x) | 删除元素 |
MINIMUM(S) | 返回最小元素 |
MAXIMUM(S) | 返回最大元素 |
SUCCESSOR(S, x) | 返回大于 |
PREDECESSOR(S, x) | 返回小于 |
不同数据结构适合不同操作组合。哈希表擅长等值查询,但不擅长有序操作;搜索树牺牲一些常数,换来顺序相关操作。
4.5 二叉搜索树
二叉搜索树 BST 满足:
中序遍历 BST 会得到非降序序列。
搜索:
text
TREE-SEARCH(x, k):
if x == nil or k == x.key:
return x
if k < x.key:
return TREE-SEARCH(x.left, k)
else:
return TREE-SEARCH(x.right, k)插入:
text
TREE-INSERT(T, z):
y = nil
x = T.root
while x != nil:
y = x
if z.key < x.key:
x = x.left
else:
x = x.right
z.parent = y
if y == nil:
T.root = z
else if z.key < y.key:
y.left = z
else:
y.right = z搜索和插入的时间都是
4.6 Successor、Predecessor 与删除
后继 SUCCESSOR(x) 是大于
- 若
有右子树,后继是右子树中的最小节点。 - 若没有右子树,向上找第一个“从左孩子走上来的祖先”。
删除节点
没有孩子:直接删除。 只有一个孩子:用孩子替代 。 有两个孩子:找 的后继 ,用 替代 ,再处理 原位置。
CLRS 中常用 TRANSPLANT(T, u, v) 表示“用子树
text
TRANSPLANT(T, u, v):
if u.parent == nil:
T.root = v
else if u == u.parent.left:
u.parent.left = v
else:
u.parent.right = v
if v != nil:
v.parent = u.parent删除操作本身也是
4.7 用 BST 排序与快速排序的联系
若把数组元素依次插入 BST,再中序遍历输出,就得到排序结果。
运行时间等于每次插入路径长度之和。若输入随机,BST 形状和快速排序递归树有相同分布,因此期望
这说明“树高”就是 BST 性能的核心。因此需要平衡搜索树。
4.8 红黑树
红黑树是一种近似平衡的 BST。每个节点有红或黑两种颜色,并满足:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 所有叶子
nil是黑色。 - 红节点的孩子都是黑色,也就是不能有连续红节点。
- 对任意节点,从该节点到所有后代叶子的简单路径包含相同数量的黑节点。
定义黑高
高度界:
含
证明思路:
- 由性质 4,任一路径上红节点不能相邻,所以至少一半节点是黑节点,
。 - 归纳证明以节点
为根、黑高为 的子树至少有 个内部节点。 - 因此
,推出 。
所以红黑树的搜索、插入、删除都是
4.9 旋转与插入修复
旋转是局部改变树形而保持 BST 中序顺序不变的操作。
左旋:
text
LEFT-ROTATE(T, x):
y = x.right
x.right = y.left
if y.left != nil:
y.left.parent = x
y.parent = x.parent
if x.parent == nil:
T.root = y
else if x == x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y红黑树插入先按 BST 插入,并把新节点染红。这样不会增加任何路径黑高,但可能违反“红节点不能有红孩子”。修复过程围绕新节点、父节点、叔节点和祖父节点分情况:
- 叔节点红:父和叔变黑,祖父变红,把问题上移。
- 叔节点黑且新节点是“内侧孩子”:先旋转转成外侧情况。
- 叔节点黑且新节点是“外侧孩子”:父变黑、祖父变红,对祖父旋转。
删除修复更复杂,本课程重点通常是理解为什么红黑树能保持
4.10 跳表
课件后半部分从“多条 express line 的有序链表”引出跳表。跳表是一组分层有序链表:
- 最底层包含所有元素。
- 上层是下层的随机抽样。
- 搜索时从最高层开始,能向右就向右,否则向下。
若每个节点以概率
- 层数期望
。 - 搜索、插入、删除期望
。
跳表和红黑树都提供有序动态集合操作。红黑树靠确定性旋转维持平衡,跳表靠随机化维持期望平衡。
作业定位
- LeetCode 215:快速选择是最贴近本讲的做法。若求第
大,目标排名是第 小。 - 堆做法来自第 2 讲,维护大小为
的最小堆;快速选择期望更快,但实现时要小心 partition 边界。
本讲易错点
- 快速选择和快速排序都用 partition,但快速选择只递归一边。
- 第
大和第 小转换时容易出现 与 的 off-by-one。 - BST 中序有序是搜索树性质的直接结果。
- BST 删除两个孩子的节点时,后继最多只有一个右孩子。
- 红黑树不是严格平衡树;它保证最长路径不超过最短路径的约两倍。
- 旋转不会改变中序序列,只改变树高和局部父子关系。
- 跳表的复杂度是期望复杂度,依赖随机提升。
自测题
- 写出
RANDOMIZED-SELECT,并说明它和快速排序的递归差别。 - 为什么 median of medians 能保证最坏
? - 证明 BST 中序遍历输出有序序列。
- 描述 BST 删除有两个孩子节点的过程。
- 写出红黑树五条性质,并说明它们如何推出高度
。 - 比较红黑树和跳表的平衡机制。