最小生成树的两种算法

最小生成树prim算法实现

所谓生成树,就是n个点之间连成n-1条边的图形。而最小生成树,就是权值(两点间直线的值)之和的最小值。

 

   

  

  首先,要用二维数组记录点和权值。如上图所示无向图:

int map[7][7];
       map[1][2]=map[2][1]=4;
       map[1][3]=map[3][1]=2;
       ......

      然后再求最小生成树。具体方法是:

1.先选取一个点作起始点,然后选择它邻近的权值最小的点(如果有多个与其相连的相同最小权值的点,随便选取一个)。如1作为起点。

visited[1]=1;

pos=1;

//用low[]数组不断刷新最小权值,low[i](0<i<=点数)的值为:i点到邻近点(未被标记)的最小距离。

low[1]=0;  //起始点i到邻近点的最小距离为0

low[2]=map[pos][2]=4;

low[3]=map[pos][3]=2;

low[4]==map[pos][4]=3;

low[5]=map[pos][5]=MaxInt;  //无法直达

low[6]=map[pos][6]=MaxInt;

 

  2.再在伸延的点找与它邻近的两者权值最小的点。

//low[]以3作当前位置进行更新

visited[3]=1;

pos=3;

low[1]=0;   //已标记,不更新

low[2]=map[1][2]=4;  //比5小,不更新

low[3]=2;  //已标记,不更新

low[4]=map[1][4]=3;   //比1大,更新后为:low[4]=map[3][4]=1;

low[5]=map[1][5]=MaxInt;//无法直达,不更新

low[6]=map[1][6]=MaxInt;//比2大,更新后为:low[6]=map[3][6]=2;

 

    3.如此类推...

 
 
     当所有点都连同后,结果最生成树如上图所示。

     所有权值相加就是最小生成树,其值为2+1+2+4+3=12。

     至于具体代码如何实现,现在结合POJ1258例题解释。代码如下:

 1 //poj-1258 
 2 #include <stdio.h> 
 3 #include <string.h> 
 4 #define MaxInt 0x3f3f3f3f 
 5 #define N 110 
 6 //创建map二维数组储存图表,low数组记录每2个点间最小权值,visited数组标记某点是否已访问 
 7 int map[N][N],low[N],visited[N]; 
 8 int n;   
 9 int prim() 
10 { 
11     int i,j,pos,min,result=0; 
12     memset(visited,0,sizeof(visited)); 
13     visited[1]=1;pos=1; //从某点开始,分别标记和记录该点 
14     for(i=1;i<=n;i++)   //第一次给low数组赋值 
15         if(i!=pos) low[i]=map[pos][i]; 
16     for(i=1;i<n;i++)    //再运行n-1次 
17     {         
18         min=MaxInt; //找出最小权值并记录位置 
19         for(j=1;j<=n;j++) 
20          if(visited[j]==0&&min>low[j]) 
21          { 
22              min=low[j];pos=j; 
23          } 
24         result+=min;    //最小权值累加 
25         visited[pos]=1;     //标记该点 
26         for(j=1;j<=n;j++)   //更新权值 
27         if(visited[j]==0&&low[j]>map[pos][j]) 
28             low[j]=map[pos][j]; 
29     } 
30     return result; 
31 } 
32   
33 int main() 
34 { 
35     int i,v,j,ans; 
36     while(scanf("%d",&n)!=EOF) 
37     { 
38         memset(map,MaxInt,sizeof(map)); //所有权值初始化为最大 
39         for(i=1;i<=n;i++) 
40             for(j=1;j<=n;j++) 
41             { 
42                 scanf("%d",&v); 
43                 map[i][j]=map[i][j]=v; 
44             } 
45             ans=prim(); 
46             printf("%d\n",ans); 
47     } 
48     return 0; 
49 }

 

http://acm.nyist.net/JudgeOnline/problem.php?pid=38

nyoj—38代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 int map[550][550],vis[550],low[550];
 7 int n;
 8 int prim()
 9 {
10     int i,j,pos,min,sum=0;
11     memset(low,0,sizeof(low));
12     memset(vis,0,sizeof(vis));
13     vis[1]=1;pos=1;
14     for(i=1;i<=n;i++)
15     if(i!=pos)
16     low[i]=map[pos][i];
17     for(i=1;i<n;i++)
18     {
19         min=200;
20         for(j=1;j<=n;j++)
21         {
22             if(!vis[j]&&low[j]<min)
23             {
24                 min=low[j];
25                 pos=j;
26             }
27         }
28         sum+=min;
29         vis[pos]=1;
30         for(j=1;j<=n;j++)
31         {
32             if(vis[j]==0&&low[j]>map[pos][j])
33             low[j]=map[pos][j];
34         }
35     }
36     return sum;
37 }
38 int main()
39 {
40     int T;
41     scanf("%d",&T);
42     while(T--)
43     {
44         int i,j,m,v,e,c;
45         int s[550];
46         memset(map,0,sizeof(map));
47         memset(s,0,sizeof(s));
48         scanf("%d %d",&n,&m);
49         //if(n==0&&m==0)
50         //{
51         //    printf("0\n");
52         //    continue;
53         //}
54         for(i=0;i<=n;i++)
55         for(j=0;j<=n;j++)
56         map[i][j]=map[j][i]=200;
57         for(i=0;i<m;i++)
58         {
59             scanf("%d %d %d",&v,&e,&c);
60             map[v][e]=map[e][v]=c;
61         }
62         for(i=0;i<n;i++)
63         scanf("%d",&s[i]);
64         sort(s,s+n); 
65         printf("%d\n",prim()+s[0]);
66     }
67     return 0;
68 }
69 //prim算法 
70 //初始化时没有把 vis数组清零导致 wa 
View Code

 

最小生成树Kruskal算法+并查集实现

 

今天刚掌握Kruskal算法,写下随笔。

对于稀疏图来说,用Kruskal写最小生成树效率更好,加上并查集,可对其进行优化。

Kruskal算法的步骤:

1.对所有边进行从小到大的排序。

2.每次选一条边(最小的边),如果如果形成环,就不加入(u,v)中,否则加入。那么加入的(u,v)一定是最佳的。

并查集:
我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。

而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

find(int x) {return p[x]==x?x:p[x]=find(p[x]);}

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。

既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。如下图所示:

 
设第i条边的端点序号和权值分别保存在u[i],v[i],w[i]中,而排序后第i小的边保存在r[i]中。(间接排序是指排序的关键字是对象的代号,而不是对象本身。)
结合hdoj1863代码如下:
 1 //hdu_1863
 2 #include <stdio.h> 
 3 #include <stdlib.h> 
 4 #include <algorithm> 
 5 #define N 150 
 6 using namespace std; 
 7 int m,n,u[N],v[N],w[N],p[N],r[N]; 
 8 int cmp(const int i,const int j) {return w[i]<w[j];} 
 9 int find(int x) {return p[x]==x?x:p[x]=find(p[x]);} 
10 int kruskal() 
11 { 
12     int cou=0,x,y,i,ans=0; 
13     for(i=0;i<n;i++) p[i]=i; 
14     for(i=0;i<m;i++) r[i]=i; 
15     sort(r,r+m,cmp); 
16     for(i=0;i<m;i++) 
17     { 
18         int e=r[i];x=find(u[e]);y=find(v[e]); 
19         if(x!=y) {ans += w[e];p[x]=y;cou++;} 
20     } 
21     if(cou<n-1) ans=0; 
22     return ans; 
23 } 
24   
25 int main() 
26 { 
27     int i,ans; 
28     while(scanf("%d%d",&m,&n)!=EOF&&m) 
29     { 
30         for(i=0;i<m;i++) 
31         { 
32             scanf("%d%d%d",&u[i],&v[i],&w[i]); 
33         } 
34         ans=kruskal(); 
35         if(ans) printf("%d\n",ans); 
36         else printf("?\n",ans); 
37     } 
38     return 0; 
39 }
View Code

我的hdu1863代码:

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1863

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 //#include <algotithm>
 5 #include <stdlib.h>
 6 using namespace std;
 7 typedef struct IN
 8 {
 9     int a;
10     int b;
11     int c;
12 }IN;
13 IN s[5000];
14 int N,M;
15 int pre[110];
16 int cmp(const void *a,const void *b)
17 {
18     return (*(IN *)a).c - (*(IN *)b).c;
19 }
20 int find(int x)
21 {
22     int i,r,t;
23     r=x;
24     while(r!=pre[r])
25     r=pre[r];
26     while(x!=r)
27     {
28         i=pre[x];
29         pre[x]=r;
30         x=i;
31     }
32     return r;
33 }
34 int kruskal()
35 {
36     int i,j,pa,pb,num=0,sum=0;
37     for(i=0;i<=M;i++)
38     pre[i]=i;
39     for(i=0;i<N;i++)
40     {
41         pa=find(s[i].a);
42         pb=find(s[i].b);
43         if(pa!=pb)
44         {
45             pre[pa]=pb;
46             sum+=s[i].c;
47             num++;
48         }
49     }
50     if(num==M-1)
51     return sum;
52     else
53     return 0;
54 }
55 int main()
56 {
57     while(scanf("%d %d",&N,&M),N)
58     {
59         int i,j,t;
60         memset(s,0,sizeof(s));
61         for(i=0;i<N;i++)
62         scanf("%d %d %d",&s[i].a,&s[i].b,&s[i].c);
63         qsort(s,N,sizeof(s[0]),cmp);
64         //for(i=0;i<N;i++)
65         //printf("%d\n",s[i].c);
66         t=kruskal();
67         if(t)
68         printf("%d\n",t);
69         else
70         printf("?\n");
71     }
72     return 0;
73 }

 

 

转载于:https://www.cnblogs.com/xl1027515989/p/3597091.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值