此文首发于Yucohny
我就是原作者
一、基本操作
1.初始化
void init() {
for(int i = 1; i < N; i++)
f[i] = i;
}
2.路径压缩
int find(int x) {
return f[x]==x ? x : f[x] = find(f[x]);
}
3.合并
void merge(int x, int y) {
f[find(x)] = find(y);
}
4.求集合数量
int size() {
int cnt = 0;
for(int i = 1; i <= n; i++)
if(f[i] == i)
cnt++;
return cnt;
}
二、例题
1.模板题:洛谷1551
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
class Union_Find_Sets{
public:
int f[N];
void init(int n) {
for(int i = 1; i <= n; i++)
f[i] = i;
}
int find(int x) {
return f[x]==x ? x : f[x] = find(f[x]);
}
void merge(int x, int y) {
f[find(x)] = find(y);
}
};
int main() {
Union_Find_Sets s;
int n, m ,q;
scanf("%d%d%d",&n,&m,&q);
s.init(n);
for(int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
s.merge(x, y);
}
for(int i = 1; i <= q; i++) {
int x, y;
scanf("%d%d", &x, &y);
if(s.find(x) == s.find(y))
puts("Yes");
else
puts("No");
}
return 0;
}
2.模板题:LeetCode 547
class Solution {
public:
int f[205];
void init() {
for(int i = 1; i <= 200; i++)
f[i] = i;
}
int find(int x) {
return f[x]==x ? x : f[x]=find(f[x]);
}
void merge(int x, int y) {
f[find(x)] = find(y);
}
int findCircleNum(vector<vector<int> >& isConnected) {
int n = isConnected.size(), cnt = 0;
init();
for(int i = 0; i < n; i++)
for(int j = i+1; j < n; j++)
if(isConnected[i][j])
merge(i+1,j+1);
for(int i = 1; i <= n; i++)
if(f[i] == i)
cnt++;
return cnt;
}
};
3.LeetCode 684
三、后言
事实上,在LeetCode上,可以用并查集解题的还有许多,很多题目都可以拿来练练手,如200,此处就不再过多举例。
对于很多纯并查集的题,我们也可以通过宽搜深搜进行解决,但是当并查集与其他数据结构或者算法结合起来时,深搜宽搜未必就有那么实用了。
而对于并查集的时间复杂度,维基百科这样说明:
对于同时使用路径压缩和按秩合并优化的不交集森林,每个查询和合并操作的平均时间复杂度仅为 O ( α ( n ) ) , α ( n ) O(\alpha (n)),\alpha (n) O(α(n)),α(n)是反阿克曼函数。由于阿克曼函数 A A A增加极度迅速,所以 α ( n ) \alpha (n) α(n)增长极度缓慢,对于任何在实践中有意义的元素数目 α ( n ) \alpha (n) α(n)均小于5,因此,也可以粗略地认为,并查集的操作有常数的时间复杂度。
实际上,这是渐近最优算法:Fredman 和 Saks 在 1989 年解释了 Ω ( α ( n ) ) \Omega (\alpha (n)) Ω(α(n))的平均时间内可以获得任何并查集。