【数据结构】并查集

来源:互联网 发布:linux打包tar.gz 编辑:程序博客网 时间:2024/06/09 14:04

上次ACM集训时,有人问我Kruskal,结果并查集他不是很清楚,就比较麻烦了。所以这次顺便写一下并查集。

并查集是一种高效的数据结构。它主要对集合进行查询合并操作。

并——将两个元素所在的集合合并为一个。

查——查看元素在哪一个集合中,进而查看两个元素是否在同一个集合内。

集——这里的集合,是用一棵表示的。

 

 我们先来说集合的表示,我们把同一个集合的元素放在同一棵树里,这样它们就会有相同的根节点,代表它们在同一个集合中。至于在元素在树里的次序是无关紧要的,我们只关心它在哪个集合(树)里。

                                                     

               集合A={1,3,5,7,9}                                              集合B={2,4,6,8,10}

接下来要进行查询操作,即找到该元素所在树的根节点,只需要不断地访问元素的父亲,父亲的父亲,以此类推,所以我们需要一个father数组,来记录树中每个元素的父节点。开始的时候,如果每个元素是独立的,我们应该把每个元素的father设为自己。

   father[i]=i;

 合并操作就更简单了,找到两个元素所在的树的根节点,将一个根设为另一个根的孩子即可。


  

合并后的集合A+B

接下来我们看具体实现。为了实现查操作,我们定义函数

int findfather(int x){  if (x==father[x]) return x;    else return findfather(father[x]);}
很容易理解,如果它的父亲是它自己,那么他就是根节点,返回自己;否则,从它的父亲寻找根节点并返回。这里我们可以加一个优化,叫做路径压缩,因为我们在找根节点的时候,一路问别人,直到找到根节点,那么回来的时候,举手之劳,顺便告诉别人根节点是谁,这样别人下次查询时直接就知道了,这样多好。
我们代码改成

if (x==father[x]) return x;  else return father[x]=findfather(father[x]);

这样一来,假设我们查询的是4号点,查询前后的树如图,我们发现多次查询后,树的深度就会变浅。

                  
             查询前                                                              查询后

接着看合并操 union_set

bool union_set(int x, int y){  int fx=findfather(x);  int fy=findfather(y);  if (fx==fy) return false;  father[fx]=fy;  return true;}

我们看到只需要找到两棵树的根节点,然后把一个的父亲设为另外一个就可以了。但是,我们发现如果遇到特殊情况,不断地合并会出现这样的情况。

                            

退化了的集合树

为了解决这个问题,我们可以设一个rank数组来记录每棵树的深度,在合并时,应让深度小的作为深度大的树的子树,将father[fx]=fy改写为

if(rank[fx]<rank[fy])father[fx]=fy;else if (rank[fx]>rank[fy] )father[fy]=fx;else //深度相同树,大树的深度才会增加{  father[fx]=fy;  rank[fy]++; }

注意,rank数组记得初始化。

至此,并查集的结构,操作我们已经解决的差不多了。让我们来看一份完整的代码:

#include<stdio.h>intfather[10000];Intrank[10000];int findfather(int x){    if (father[x]==x) return x;    else returnfather[x]=findfather(father[x]); //路径压缩}void union_set(int x, int y){    int fx=findfather(x);    int fy=findfather(y);    if(rank[fx]<rank[fy])father[fx]=fy;    else if(rank[fx]>rank[fy] )father[fy]=fx;    else    {        father[fx]=fy;        rank[fy]++;    }}int main(){    for (i=1; i<=10000; i++)        father[i]=i;    for (i=1; i<=10000; i++)        rank[i]=1;    int n; //n条指令    scanf("%d",&n);    for (i=1; i<=n; i++)    {        int x,y;        scanf("%d%d",&x,&y);        union_set(x,y);    }}