文章目录
- [1. 什么是并查集?](#1. 什么是并查集?)
- [2. 并查集的实现](#2. 并查集的实现)
- [3. 并查集的应用](#3. 并查集的应用)
- [4. 并查集的复杂度分析](#4. 并查集的复杂度分析)
- [5. 总结](#5. 总结)
1. 什么是并查集?
并查集(Union-Find) 是一种用于管理 不相交集合(Disjoint Sets) 的数据结构,主要支持以下两种操作:
Find(u)
:查找元素u
所属的集合(通常返回其根节点)。Union(u, v)
:合并两个元素u
和v
所在的集合。
并查集广泛应用于:
- 图的连通性问题(如Kruskal算法求最小生成树)。
- 动态连通性问题(如社交网络中的好友关系)。
- 游戏开发(如像素连通性检测)。
2. 并查集的实现
(1)基本实现
初始时,每个元素独立构成一个集合,用数组 parent[]
存储父节点:
cpp
vector<int> parent(n);
for (int i = 0; i < n; i++) {
parent[i] = i; // 初始化,每个节点的父节点是自己
}
-
Find(u)
(路径压缩优化):cppint find(int u) { if (parent[u] != u) { parent[u] = find(parent[u]); // 路径压缩 } return parent[u]; }
-
Union(u, v)
(按秩合并优化):cppvector<int> rank(n, 0); // 记录树的深度 bool unionSets(int u, int v) { int rootU = find(u); int rootV = find(v); if (rootU == rootV) return false; // 已经在同一集合 if (rank[rootU] > rank[rootV]) { parent[rootV] = rootU; // 小树合并到大树 } else if (rank[rootU] < rank[rootV]) { parent[rootU] = rootV; } else { parent[rootV] = rootU; rank[rootU]++; // 深度相同,合并后深度+1 } return true; }
(2)优化策略
优化方法 | 作用 | 时间复杂度 |
---|---|---|
路径压缩(Path Compression) | 使 Find 操作接近 O ( 1 ) O(1) O(1) | O ( α ( n ) ) O(α(n)) O(α(n)) |
按秩合并(Union by Rank) | 避免树过高,保持平衡 | O ( α ( n ) ) O(α(n)) O(α(n)) |
其中,α(n)
是 反阿克曼函数,增长极其缓慢,可以认为是常数时间。
3. 并查集的应用
(1)检测无向图的环
cpp
bool hasCycle(vector<vector<int>>& edges, int n) {
UnionFind uf(n);
for (auto& edge : edges) {
int u = edge[0], v = edge[1];
if (!uf.unionSets(u, v)) {
return true; // 已经连通,说明有环
}
}
return false;
}
(2)Kruskal算法求最小生成树(MST)
⭐算法OJ⭐连接所有点的最小费用【最小生成树】(C++实现)Min Cost to Connect All Points
cpp
int kruskalMST(vector<vector<int>>& edges, int n) {
sort(edges.begin(), edges.end()); // 按权重排序
UnionFind uf(n);
int res = 0;
for (auto& edge : edges) {
int cost = edge[0], u = edge[1], v = edge[2];
if (uf.unionSets(u, v)) {
res += cost;
}
}
return res;
}
(3)朋友圈问题
cpp
int findCircleNum(vector<vector<int>>& isConnected) {
int n = isConnected.size();
UnionFind uf(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (isConnected[i][j]) {
uf.unionSets(i, j);
}
}
}
int circles = 0;
for (int i = 0; i < n; i++) {
if (uf.find(i) == i) circles++; // 统计根节点数量
}
return circles;
}
4. 并查集的复杂度分析
操作 | 无优化 | 路径压缩 | 路径压缩 + 按秩合并 |
---|---|---|---|
Find | O ( n ) O(n) O(n) | O ( α ( n ) ) O(α(n)) O(α(n)) | O ( α ( n ) ) O(α(n)) O(α(n)) |
Union | O ( n ) O(n) O(n) | O ( α ( n ) ) O(α(n)) O(α(n)) | O ( α ( n ) ) O(α(n)) O(α(n)) |
其中, α ( n ) α(n) α(n) 是 反阿克曼函数(Inverse Ackermann Function) ,在大多数实际应用中接近于 O ( 1 ) O(1) O(1)。
可以认为: α ( n ) α(n) α(n) 是 "阿克曼函数需要递归多少次才能超过 n"。由于阿克曼函数增长极快, α ( n ) α(n) α(n) 几乎不会超过 5(在现实计算中)
5. 总结
并查集 是一种高效管理 不相交集合 的数据结构。
- 路径压缩 + 按秩合并 可以使其接近 O ( 1 ) O(1) O(1) 时间。
- 典型应用:检测环、最小生成树、动态连通性问题。