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找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先
 
并查集的优化
1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次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  }

posted on 2012-08-13 16:17  mycapple  阅读(284)  评论(0编辑  收藏  举报

导航