并查集的定义
- 合并:Union
- 查找:Find
- 集合:Set
并查集其实就是一个数组
int father[N];
father[i]
表示元素i
的父亲结点,而父亲结点本身也是这个集合内的元素(1≤i≤N)
father[i] = i
说明元素i是根结点
并查集的基本操作
初始化
for(int i = 1; i <= N; i++){
father[i] = i; // 初始时,每个元素都是独立的一个集合
}
查找
int findFather(int x){
if(x == father[x]) return x;
else return Findfather(father[x]);
}
合并
合并是指把两个集合合并成一个集合
步骤
- 对于给定的两个元素a和b,判断他们是否属于同一个集合。即调用
findFather
函数对这两个元素查找根结点,然后再判断其根结点是否相同 - 合并两个集合:在上一步中已经获得了两个元素的根结点faA和faB,因此只需要把其中一个的父亲结点指向另一个结点
void Union(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA != faB){
father[faA] = faB;
}
}
为了保证不产生环:只对不同集合进行合并。所以,并查集产生的每一个集合都是一棵树
路径压缩
当许多元素形成一条链时,查找函数的效率就会很低,所以要进行路径压缩
把当前查询结点的路径上的所有结点的父亲都指向根结点
步骤
- 按照原先的写法获得x的根结点r
- 重新从x开始走一遍寻找根结点的过程,把路径上经过的所有结点的父亲全部改为根结点r
int findFather(int v){
if(v == father[v]) return v; // 找到根结点
else{
int F = findFather(father[v]);
father[v] = F;
return F;
}
}
例题
#include<cstdio>
const int MAXN = 110;
bool isRoot[MAXN];
int father[MAXN];
void initial(int n){
for(int i = 1; i <= n; i++){
father[i] = i;
isRoot[i] = false;
}
}
int findFather(int x){
if(x == father[x]) return x;
else{
int F = findFather(father[x]);
father[x] = F;
return F;
}
}
void unions(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if(faA != faB){
father[faA] = faB;
}
}
int main(){
int n, m, a, b;
scanf("%d%d", &n, &m);
initial(n);
for(int i = 0; i < m; i++){
scanf("%d%d", &a, &b);
unions(a, b);
}
for(int i = 1; i <= n; i++){
isRoot[findFather(i)] = true;
}
int ans = 0;
for(int i = 1; i <= n; i++){
ans += isRoot[i];
}
printf("%d
", ans);
return 0;
}
Write by Gqq