参考文章:http://blog.csdn.net/dellaserss/article/details/7724401/
定义
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
举例
首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。像畅通工程这题,问还需要修几条路,实质就是求有几个连通分支。如果是1个连通分支,说明整幅图上的点都连起来了,不用再修路了;如果是2个连通分支,则只要再修1条路,从两个分支中各选一个点,把它们连起来,那么所有的点都是连起来的了;如果是3个连通分支,则只要再修两条路……
以下面这组数据输入数据来说明
4 2 1 3 4 3
第一行告诉你,一共有4个点,2条路。下面两行告诉你,1、3之间有条路,4、3之间有条路。那么整幅图就被分成了1-3-4和2两部分。只要再加一条路,把2和其他任意一个点连起来,畅通工程就实现了,那么这个这组数据的输出结果就是1。好了,现在编程实现这个功能吧,城镇有几百个,路有不知道多少条,而且可能有回路。 这可如何是好?
实现思路
int pre[1000 ];
int Find(int x) { int r=x; while(r!=pre[r]) r=pre[r]; return r; }
void join(int x,int y) { int fx=Find(x),fy=Find(y); if(fx!=fy) { pre[fy]=fx; } }
并查集由一个整数型的数组和两个函数构成。数组pre[]记录了每个点的前导点是什么,函数find是查找,join是合并。
但是这么实现有一个缺点:最后的树状结构会变成什么样,我也完全无法预计,一字长蛇阵也有可能。这样查找的效率就会比较低下。最理想的情况就是所有人的直接上级都是根节点,一共就两级结构,只要找一次就找到根节点了。哪怕不能完全做到,也最好尽量接近。这样就产生了路径压缩算法,查找的时候动态优化树结构。 修改后的查找算法如下:
int Find(int x) { int r=x; while(r!=pre[r]) r=pre[r]; //路径压缩 int i=x,j; while(pre[i]!=r) { j=pre[i]; pre[i]=r; i=j; } return r; }
杭电1232畅通工程 完整答案
1 #include<iostream> 2 using namespace std; 3 4 int pre[1050]; 5 bool t[1050]; //t 用于标记独立块的根结点 6 7 int Find(int x) 8 { 9 int r=x; 10 while(r!=pre[r]) 11 r=pre[r]; 12 13 int i=x,j; 14 while(pre[i]!=r) 15 { 16 j=pre[i]; 17 pre[i]=r; 18 i=j; 19 } 20 return r; 21 } 22 23 void mix(int x,int y) 24 { 25 int fx=Find(x),fy=Find(y); 26 if(fx!=fy) 27 { 28 pre[fy]=fx; 29 } 30 } 31 32 int main() 33 { 34 int N,M,a,b,i,j,ans; 35 while(scanf("%d%d",&N,&M)&&N) 36 { 37 for(i=1;i<=N;i++) //初始化 38 pre[i]=i; 39 40 for(i=1;i<=M;i++) //吸收并整理数据 41 { 42 scanf("%d%d",&a,&b); 43 mix(a,b); 44 } 45 46 47 memset(t,0,sizeof(t)); 48 for(i=1;i<=N;i++) //标记根结点 49 { 50 t[Find(i)]=1; 51 } 52 for(ans=0,i=1;i<=N;i++) 53 if(t[i]) 54 ans++; 55 56 printf("%d\n",ans-1); 57 58 } 59 return 0; 60 }//dellaserss
原文思路更加详细与清晰,如有疑惑推荐阅读原文