并查集、MST 与离线激活 · HTML
数学 / 逻辑形态
对象集 V 上执行 union(a,b)、find(x)、connected(a,b);关系是等价关系,边只增加。
识别信号
- 反复合并两组并查询同组
- 只关心连通而不关心具体路径
- 关系具有传递性
- 边在线加入而不删除
- 需要组件数或组件大小
容易误判的相似信号
| Signal | Why it is different |
|---|
| 需要路径或距离 | DSU 丢弃图内路径;应使用 BFS、Dijkstra 或显式图。 |
| 任意在线删边 | 普通 DSU 不能拆集合;考虑倒序离线、rollback DSU 或动态连通结构。 |
核心不变量
每个集合是一棵父指针树,根是唯一代表元;size 仅在根处有效。
状态、转移与处理顺序
| Item | Definition |
|---|
| State | parent、根规模 size、当前组件数 components。 |
| Transition | find 路径压缩;union 先找根,再把小树根接到大树根。 |
| Frontier / order | 无搜索前沿;已处理关系被压缩进根结构,未处理操作仍在输入流。 |
Python 骨架
class DSU:
def __init__(self, n):
self.p = list(range(n))
self.sz = [1] * n
self.components = n
def find(self, x):
while x != self.p[x]:
self.p[x] = self.p[self.p[x]]
x = self.p[x]
return x
def union(self, a, b):
a, b = self.find(a), self.find(b)
if a == b: return False
if self.sz[a] < self.sz[b]: a, b = b, a
self.p[b] = a
self.sz[a] += self.sz[b]
self.components -= 1
return True
def connected(self, a, b):
return self.find(a) == self.find(b)
正确性思路
- 初始每个元素自成集合,不变量成立。
- union 只连接不同集合的根,恰好合并两个等价类。
- 路径压缩只改挂同树祖先,不改变代表元。
- 归纳得 find 相等当且仅当已处理关系使两点连通。
复杂度
| Time | Space | Parameters |
|---|
| m 次操作总计 O((n+m) α(n)) | O(n) | n 为对象数,m 为操作数,α 为反 Ackermann 函数 |
边界条件
- 重复 union 不应修改计数
- a==b 时天然连通
- size 必须从根读取
- 标签稀疏时先坐标压缩
- n=0 的接口语义需约定
错误模式
| Pattern | Why it fails / fix |
|---|
| 直接比较 parent | parent 可能尚未压缩,必须比较 find。 |
| 合并普通节点 | 必须连接两个根,否则规模和树高失控。 |
| 重复合并仍减计数 | 两根相同时应立即返回。 |
选择与排除规则
- 只有增边连通查询:首选 DSU。
- 需要实际路径或距离:使用图搜索。
- 删边能倒序变成加边:转离线 DSU。
- 对象标签不连续:先映射到紧凑整数。
相关模板
原创微型例子
| 环节 | 内容 |
|---|
| Result | {0,1,3,4} 成组,2 独立,components=2。 |
| Scenario | 5 台设备依次接通 0-1、3-4、1-4。 |
| Walkthrough | 前两次形成两棵树;第三次找到两根并合并,connected(0,3) 变真。 |
Recall prompts
我的补充