并查集是什么
并查集就如字面上,是一个具有合并(union)与查找(find)功能的集合。它的本质是一个等价类(具有自反性、对称性、传递性的图)。
(该段是自反、对称、传递的说明,已学过的可以跳过)
通俗地解释下自反、对称以及传递,如果每个人都和自己互为亲人,即“我是我的亲人”,那么可以称这是一种自反的;假如我是你的亲人,那么也可以说你是我的亲人,这样的关系称为对称的,如果只是单方面的,比如我喜欢你,你不喜欢我,这样的就不能称之为对称的;你是我的亲人,他是你的亲人,他也是我的亲人,即"我的亲人的亲人还是我的亲人",这样的关系称为传递的
作用: 并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)
并查集的实现
由于满足自反、对称zhao、传递,那么对于并查集中所有点,查找他们的“亲人”的“亲人”若干次,总会落到同一个点上(所有人都可以成为这个点),我们称这些点是同一个集合,并且将该集合结构构造成一棵树,将任意一个共同“亲人”作为根节点,查找(find)功能就是要查找根节点。
对于这样的树结构,我们是这样构造的,设置一个fa[]数组用于存储当前点的父节点,对于根节点,他的父节点就是他自己即fa[root]=root一直查找+
find函数的实现
对于当前点一直向上找下去直到满足fa[root]==root,即找到根节点。
int find(int x){
while(fa[x] != x)
x = fa[x];
return x;
}
union函数的实现
若为同一个根,则已在集合中,无需添加。
void union(int x,int y){
int r1=find(x);
int r2=find(y);bin
if(r1!=r2)
fa[r1]=r2;
}
并查集的优化
按秩合并
按秩合并中的秩说白了就是树的深度,因为find函数的复杂度是取决于树的深度的,所以我们希望将秩小的合并到秩大的根上去,尽可能减少秩增大的可能,以减少树的深度。优化体现在合并(union)操作上。
void union(int x,int y){
int r1=find(x);
int r2=find(y);
if(r1!=r2){
if(rank[r1]<rank[r2]){
fa[r2]=r1;
}
else{
if(rank[r1]==rank[r2]) rank[r2]++;
fa[r1]=r2;
}
}
}
路径压缩
在并查集中实际上我们并不关心某个节点的父亲是谁,或者他的父亲的父亲是谁。我们只在乎他的根节点是谁,因此我们根据“我的爸爸的爸爸是我的爸爸”的方法,将根节点下所有的点直接与根节点连接,这样就可以极大的减少树的深度。优化体现在查找(find)功能上。
int find(int x){
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
模板题
题解;
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define go(i,l,r) for(int i=l;i<=r;i++)
#define rgo(i,r,l) for(int i=r;r>=l;i--)
const int maxn(10000+10);
int fa[maxn],rank[maxn];
int n,m;
//求父亲
int father(int x){
if(x==fa[x]) return x;
if(x!=fa[x]){
fa[x]=father(fa[x]);
}
return fa[x];
}
//建立联合
void Union(int x,int y){
int r1=father(x);
int r2=father(y);
if(rank[r1]<rank[r2]){
fa[r2]=r1;
}
else{
if(rank[r1]==rank[r2]) rank[r2]++;
fa[r1]=r2;
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
go(i,1,n) fa[i]=i;
go(i,1,m){
int z,x,y;
cin>>z>>x>>y;
if(father(x)!=father(y)){
if(z==1) Union(x,y);
else cout<<"N"<<endl;
}
else {
if(z==2) cout<<"Y"<<endl;
}
}
return 0;
}