并查集是一种对集合的处理。一开始我并没有认真分析过这种数据结构。以为就是对一个数组中的元素的普通集合操作。
我最初的实现方法是这样的;
定义一个set数组表示集合
定义一个element数组表示元素。
set[i]为0表示该集合为空;
element[i]表示该元素所处集合的位置。
例如表示集合{1,3,5,7,9},{2,4},{6,8,10}
set={1,1,1,0,0,0....};
element={1,2,1,2,1,3,1,3,1,3};
下面以杭电1232、1233为例;
#include<stdio.h> #include<math.h> #include<string.h> int main() { int m,n,i,j,a[1005],v1,v2,set,b[1005],count; while(~scanf("%d",&n),n) { scanf("%d",&m); if(m==0) { printf("%d ",n-1); continue; } memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); set=1; scanf("%d %d",&v1,&v2); a[v1]=set,a[v2]=set; set++; for(i=0;i<m-1;i++) { scanf("%d %d",&v1,&v2); if(a[v1]!=0&&a[v2]!=0&&a[v1]!=a[v2]) { int temp=a[v2]; for(j=1;j<=n;j++) { if(a[j]==temp) a[j]=a[v1]; } } else if(a[v1]&&a[v2]==0) { a[v2]=a[v1]; } else if(a[v1]==0&&a[v2]) { a[v1]=a[v2]; } else if(a[v1]==0&&a[v2]==0) { a[v1]=a[v2]=set; set++; } } for(i=1,count=0;i<=n;i++) { if(a[i]==0) count++; else if(a[i]&&b[a[i]]==0) { count++; b[a[i]]=1; } } printf("%d ",count-1); } }
我们可以发现这个算法的时间主要花在集合生成以及元素合并这一块;
例如要将我们刚才所说的集合2与集合3合并;就要把element数组中集合2的元素都改成3;或者要把集合3中的元素改成2;
这个算法的发杂性在一般情况下是线性的;
而另一种并查集就与之高效的多。利用路径压缩。和树的方式将并查集巧妙地在一个数组中实现并且调理清晰;
合并操作过程只是将树一棵树与另一棵树合并。在平摊意义下是常数级别的
#include<stdio.h> #include<math.h> #include<string.h> int a[1005]; int find(int x){return a[x]==x?x:a[x]=find(a[x]);} int main() { int m,n,i,j,v1,v2,count,x,y; while(~scanf("%d",&n),n) { scanf("%d",&m); for(i=1;i<=n;i++) a[i]=i; for(i=0;i<m;i++) { scanf("%d %d",&v1,&v2); x=find(v1);y=find(v2); if(x!=y) a[x]=y; } for(i=1,count=0;i<=n;i++) if(a[i]==i) count++; printf("%d ",count-1); } }