算法
并查集(DSU)
近 O(1) 合并与查找——动态连通性的首选数据结构
并查集概览
并查集(Disjoint Set Union,DSU)支持两种操作:Union(合并两个集合)和 Find(查询某元素属于哪个集合)。配合路径压缩与按秩合并,两种操作均摊近 O(1)(精确为反阿克曼函数 α(n))。
操作复杂度
操作均摊时间说明
find(x)O(α(n)) ≈ O(1)路径压缩后几乎常数
union(x, y)O(α(n)) ≈ O(1)按秩合并保持树高
空间O(n)parent[] 与 rank[] 数组
DSU vs BFS/DFS
场景推荐
离线批量边、Kruskal MSTDSU
在线动态连通性查询DSU
需要具体路径或层次BFS/DFS
有向图拓扑排序BFS/DFS
基础实现
两个优化缺一不可:
两大优化
- 路径压缩:find() 时将沿途所有节点直接挂到根节点,使后续查询更快
- 按秩合并:union() 时将较矮的树挂到较高的树根下,控制树高不超过 O(log n)
// DSU with path compression + union by rank
struct DSU {
vector<int> parent, rank;
int components;
DSU(int n) : parent(n), rank(n, 0), components(n) {
iota(parent.begin(), parent.end(), 0); // parent[i] = i
}
int find(int x) {
if (parent[x] != x)
parent[x] = find(parent[x]); // path compression
return parent[x];
}
bool unite(int x, int y) {
int rx = find(x), ry = find(y);
if (rx == ry) return false; // already connected
if (rank[rx] < rank[ry]) swap(rx, ry);
parent[ry] = rx; // attach shorter tree under taller
if (rank[rx] == rank[ry]) rank[rx]++;
components--;
return true;
}
bool connected(int x, int y) { return find(x) == find(y); }
};按大小 vs 按秩:按秩(rank)合并与按大小(size)合并均可。按大小更直观(rank[] 换成 size[],初始值为 1),效果相同。
练习:并查集 — 连通分量计数 — 代码练习
测试用例(3 个)
用例 1
输入:
5 3
0 1
1 2
3 4期望:
2
用例 2
输入:
4 0期望:
4
用例 3
输入:
4 3
0 1
1 2
2 3期望:
1
加权并查集
加权 DSU 在 parent[] 之外维护 weight[] 表示节点到根的相对权重(如距离、比值)。find() 时同步压缩路径并累加权重,union() 时根据已知关系设置权重差。
典型题目
- 399. 除法求值(边权为比值,加权 DSU 存对数或比值)
- 1061. 字典序最小等价字符串(等价类合并)
- 547. 省份数量(基础连通分量计数)
注意:加权 DSU 的权重语义取决于问题定义。比值类问题常用乘法而非加法合并,需仔细推导路径压缩时的权重更新公式。
练习:并查集 — 冗余边检测 — 代码练习
测试用例(3 个)
用例 1
输入:
4 5
0 1
1 2
2 3
0 3
1 3期望:
4
用例 2
输入:
3 3
0 1
1 2
0 2期望:
3
用例 3
输入:
2 2
0 1
0 1期望:
2
常见应用模式
模式速查
模式说明典型题
连通分量数初始化 cnt=n,每次真正合并时 cnt--547 省份数量
冗余边检测依次加边,若两端已连通则为冗余边684 冗余连接
Kruskal MST按权升序加边,DSU 判断是否成环1584 最小代价连通所有点
字符串等价类字符映射到整数,按等价关系合并1061 字典序最小等价字符串
// Detect the first redundant edge in an undirected graph
int findRedundant(vector<pair<int,int>>& edges, int n) {
DSU dsu(n + 1);
for (auto& [u, v] : edges) {
if (!dsu.unite(u, v)) return /* this edge is redundant */ u; // or track index
}
return -1;
}// Kruskal's MST — sort edges by weight, add if not creating a cycle
int kruskal(int n, vector<tuple<int,int,int>>& edges) { // (weight, u, v)
sort(edges.begin(), edges.end());
DSU dsu(n);
int totalCost = 0, edgesUsed = 0;
for (auto& [w, u, v] : edges) {
if (dsu.unite(u, v)) {
totalCost += w;
if (++edgesUsed == n - 1) break; // MST complete
}
}
return totalCost;
}典型题目
- 547. 省份数量
- 684. 冗余连接
- 685. 冗余连接 II(有向图)
- 721. 账户合并
- 399. 除法求值(加权)
练习:并查集 — Kruskal 最小生成树 — 代码练习
测试用例(3 个)
用例 1
输入:
4 5
0 1 1
0 2 4
1 2 2
1 3 5
2 3 1期望:
4
用例 2
输入:
3 3
0 1 10
1 2 5
0 2 3期望:
8
用例 3
输入:
2 1
0 1 7期望:
7
练习:并查集 — 连通性查询 — 代码练习
测试用例(3 个)
用例 1
输入:
5 3
0 1
1 2
3 4
3
0 2
0 3
3 4期望:
1
0
1
用例 2
输入:
3 1
0 1
2
0 1
1 2期望:
1
0
用例 3
输入:
4 4
0 1
1 2
2 3
0 3
2
0 3
1 3期望:
1
1