初识并查集

该文仅供初学者初步认识并查集以及并查集的基本操作。

先通过以下问题引入并查集:

有n个盗贼,有m条信息,每条信息表示i盗贼和j盗贼属于同一团伙,询问共有多少团伙。

简化问题,就是有n个点,m条边连接这些点,问最后有多少个独立的团。

每条信息连接的两个盗贼属于同一团伙,设为团伙k,那么之后无论哪个盗贼与这个团伙里的任意盗贼相连,也都是属于团伙k的。我们假设团伙k是有组织有纪律的,每个人都有他的领导,那么我们可以以该团伙的最高领导的编号来当做该团伙k的编号。假设我们已经知道了所有人的领导是谁,那么最后我们只要知道有几个最高领导就知道有多少个团伙了!

那么我们可以进行以下操作:

1、假设每个盗贼都是最高领导(为方便操作,我们把所有盗贼的领导都记为自己编号,那么之后只要知道哪些盗贼的领导编号是自己的编号,就知道哪些盗贼是团伙的最高领导了)

for(int i=1;i<=n;i++) fa[i]=i;(这里的fa数组相当于记录领导编号的数组)

2、对于每一条信息,对于i和j,我们先找到当前i所在团伙的最高领导,再找到j所在团伙的最高领导,分别记为i1,j1。

int find(int x){    

    while(x!=fa[x]) x=fa[x];

    return x;

}

①如果i1==j1 ,说明这两个盗贼所在的团伙的最高领导是同一个人,那么两个盗贼就是在同一个团伙,这条信息就无法提供更多资料,就可以结束对这条信息的分析了。

②如果i1!=j1 , 说明这两个盗贼在两个团伙里,那么我们可以假设j1是i1的领导(因为我们并不在意究竟谁才是最高领导,我们只在意最高领导的数量),那么fa[i1]=j1。 我们就把这两个团伙给合并为一个团伙了。

3、最后我们从1到n循环,看哪几个盗贼是最高领导,就是看哪几个位置的fa[i]==i,就知道有多少个盗贼团伙了!


综上:

初始化:

 for(int i=1;i<=n;i++) fa[i]=i;

进行合并操作:(我们把每条信息记为x,y)

   int getfather(int x){

        if(fa[x]==x) return x;else return getfather(fa[x]);

    }

       for(int i=1;i<=m;i++){

            scanf("%d%d",&x,&y);

            int xx=getfather(x),yy=getfather(y);

            if(xx==yy)continue;

            fa[xx]=yy;

        }

最后查询:

for(int i=1;i<=n;i++) if(i==fa[i]) ans++;

这样这题就能完成了!

但是有一个问题,就是大家可以发现getfather()这个函数效率很低,如果连接是连成一条线的话(比如fa[1]=1;fa[2]=1;fa[3]=2;fa[4]=3......),调用getfather()的效率就是O(n)。我们可以发现,因为我们并不在意各个团伙中的具体的领导关系,甚至只要知道一个最高领导,其他的全都是直接连接到最高领导也是能起到同样的作用的。那么我们就能进行简化:

int getfather(int x){

if(x==fa[x]) return x;return fa[x]=getfather(fa[x]);

}

这一段代码里,我们可以看到 fa[x]=getfather(fa[x])  ,为了方便理解,我们分开来看。

先求出int u=getfather(fa[x]);

然后赋值:fa[x]=u;

最后返回:return fa[x];

这样就压缩了我们的连接路径。

getfather(fa[x]) 这样求出的就是最高领导的编号(设为p),所以u=p。所以fa[x]=p。这样对于x这个盗贼,我们直接让他位于最高领导的领导下,那么下次我们通过x这个点找到这个团伙的最高领导时,只要O(1)的时间了,这样就大大降低了复杂度。用这种方式优化,可以使得平摊的复杂度为O(1)。

#include
using namespace std;
int n,m,fa[1005],ans;
int gf(int x){
	if(fa[x]==x)return x;return fa[x]=gf(fa[x]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		int xx=gf(x),yy=gf(y);
		if(xx==yy) continue;
		fa[xx]=yy;
	}
	for(int i=1;i<=n;i++) if(fa[i]==i) ans++;
	printf("%d\n",ans);
}

这就是并查集,有合并操作,就是fa[xx]=yy;有查询操作 gf(int x),来查询祖先。

可以查询有多少个团。我们为每个点加上num属性的话,也可以计算每个团中的点的个数。

给出几道练手的习题:

hdu1232
poj1611
hdu1213

你可能感兴趣的:(初识并查集)