所谓生成树,就是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 ",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 "); 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 ",prim()+s[0]); 66 } 67 return 0; 68 } 69 //prim算法 70 //初始化时没有把 vis数组清零导致 wa
最小生成树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]所在树的根结点。
既然每棵树表示的只是一个集合,因此树的形态是无关紧要的,并不需要在“查找”操作之后保持树的形态不变,只要顺便把遍历过的结点都改成树根的儿子,下次查找就会快很多了。如下图所示:
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 ",ans); 36 else printf("? ",ans); 37 } 38 return 0; 39 }
我的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 ",s[i].c); 66 t=kruskal(); 67 if(t) 68 printf("%d ",t); 69 else 70 printf("? "); 71 } 72 return 0; 73 }