HDU 1232 畅通工程
地址:http://acm.hdu.edu.cn/showproblem.php?pid=1232
思路:并查集的运用,最小生成树
并查集的精髓(即它的三种操作,结合实现代码模板进行理解):
1、Make_Set(x) 把每一个元素初始化为一个集合
n初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、Find_Set(x) 查找一个元素所在的集合
n查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先
3、Union(x,y) 合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先
并查集的优化
1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找
2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
1 int father[MAX]; /* father[x]表示x的父节点*/ 2 3 int rank[MAX]; /*rank[x]表示x的秩*/ 4 5 6 7 void Make_Set(int x) 8 9 { 10 11 father[x] = x; //根据实际情况指定的父节点可变化 12 13 rank[x] = 0; //根据实际情况初始化秩也有所变化 14 15 } 16 17 /* 查找x元素所在的集合,回溯时压缩路径*/ 18 19 int Find_Set(int x) 20 21 { 22 23 if (x != father[x]) 24 25 { 26 27 father[x] = Find_Set(father[x]); //这个回溯时的压缩路径是精华 28 29 } 30 31 return father[x]; 32 33 } 34 35 /* 36 37 按秩合并x,y所在的集合 38 39 下面的那个if else结构不是绝对的,具体根据情况变化 40 41 但是,宗旨是不变的即,按秩合并,实时更新秩。 42 43 */ 44 45 void Union(int x, int y) 46 47 { 48 49 x = Find_Set(x); 50 51 y = Find_Set(y); 52 53 if (x == y) return; 54 55 if (rank[x] > rank[y]) 56 57 { 58 59 father[y] = x; 60 61 } 62 63 else 64 65 { 66 67 if (rank[x] == rank[y]) 68 69 { 70 71 rank[y]++; 72 73 } 74 75 father[x] = y; 76 77 } 78 79 }
代码如下:
1 #include <stdio.h> 2 #include<stdlib.h> 3 int bin[1002]; 4 int findx(int x) //查找祖先节点 5 { 6 int r=x; 7 while(bin[r]!=r) //父亲节点定义是他自己的父亲是他自己,比如说4的节点的父亲是3,不是他自己,那么.... 8 r=bin[r]; //那么将r的父亲3赋给4 ,即实现递归上移,一直找到祖先节点 9 return r; 10 } 11 void merge(int x,int y) //合并两个树 12 { 13 int fx,fy; 14 fx=findx(x); //找到x的祖先 15 fy=findx(y); // 找到y的祖先 16 if(fx!=fy) //如果不相等,那么合并,将y的祖先作为x的祖先的祖先 17 bin[fx]=fy; //将y的祖先作为x的祖先的祖先 18 } 19 int main() 20 { 21 int n,m,i,x,y,count; 22 while(scanf("%d",&n),n) 23 { 24 for(i=1;i<=n;i++) 25 bin[i]=i; //初始化,将每个城镇作为一个单独的树根节点 26 for(scanf("%d",&m);m>0;m--) 27 { 28 scanf("%d %d",&x,&y); 29 merge(x,y); //每两个城镇进行一次连接,在连接的过程中进行查找,查找到祖先合并 30 } 31 for(count=-1,i=1;i<=n;i++) //count之所以初值为-1,是由于n个节点需要n-1条边连接,这样的话最后count就不用加1了 32 if(bin[i]==i) //如果当前节点的父节点就是他本身,那么count++,记录还需要多少条道路 33 count++; 34 printf("%d\n",count); 35 } 36 system("pause"); 37 return 0; 38 }
1 另一简单代码 2 #include <stdio.h> 3 const int N=1000; 4 int set[N]; 5 int find(int x) 6 { 7 return set[x]==x ? x : set[x]=find(set[x]); 8 } 9 void merge(int x,int y) 10 { 11 set[find(x)]=find(y); 12 } 13 int main() 14 { 15 int n,m; 16 while (scanf("%d%d",&n,&m) && n) 17 { 18 int i; 19 for (i=1;i<=n;i++) set[i]=i; 20 while (m--) 21 { 22 int x,y; 23 scanf("%d%d",&x,&y); 24 merge(x,y); 25 } 26 int ans=-1; 27 for (i=1;i<=n;i++) 28 if (set[i]==i) ans++; 29 printf("%d\n",ans); 30 } 31 }