首先,图论中的最小生成树问题就是给出一个大小为n*m邻接矩阵或者n个顶点m条边(包含每条边路径花费)的数据,让我们计算使得这n个顶点直接或间接联通所需要的最小花费。
其次,所给的数据分为稀疏图和稠密图,对于一个图,理论上n个顶点可以有n*(n-1)条边,如果该图中存在的边数m远小于n*(n-1),可以称之为稀疏图,如果该图中存在的边数m接近n*(n-1),可以称之为稠密图。
接下来,先总结一下prim算法。我们将图中所有的顶点分为两类:树顶点(已被选入生成树的顶点)和非树顶点(还未被选入生成树的顶点)。首先选择任意一个顶点加入生成树。接下来要找出一条边添加到生成树,这需要枚举每一个树顶点到每一个非树顶点所有的边,然后找到最短边加入到生成树。按照此方法重复n-1次,直到将所有顶点都加入到生成树中。
prim算法模板题:http://hihocoder.com/problemset/problem/1097
AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<string.h> 3 int e[1100][1100],book[1100],dis[1100]; // 顶点数n<=1000 4 const int inf=99999999; 5 int main() 6 { 7 int n,i,j,k,c,sum,min; 8 while(scanf("%d",&n) != EOF) 9 { 10 for(i=1;i<=n;i++) 11 for(j=1;j<=n;j++) 12 scanf("%d",&e[i][j]); 13 for(i=1;i<=n;i++) 14 dis[i]=e[1][i]; 15 16 memset(book,0,sizeof(book)); 17 book[1]=1; 18 c=1; 19 sum=0; 20 while(c < n) 21 { 22 min=inf; 23 for(j=0,i=1;i<=n;i++) 24 { 25 if(!book[i] && dis[i] < min) //注意是且的关系,该点没有被访问过而且距离生成树最近 26 { 27 min=dis[i]; 28 j=i; 29 } 30 } 31 book[j]=1; //标记该点被访问过 32 c++; //计加入的顶点数 33 sum += dis[j]; //累计花费 34 for(k=1;k<=n;k++) //更新生成树到各各非树顶点的距离 35 { 36 if(!book[k] && dis[k] > e[j][k]) 37 dis[k]=e[j][k]; 38 } 39 } 40 printf("%d ",sum); 41 } 42 return 0; 43 }
上述算法的时间复杂度是O(N*N),适用于顶点数较少的稠密图(因为顶点数太多,一是开不了那么大的数组,二是容易超时)。如果数组开不了那么大,又想用prim算法的话,可以使用邻接表来存储图,但是这里更推荐使用Kruskal算法,因为较容易实现,不局限于数组的大小,效率也很高。
再总结一下Kruskal算法,首先将边按照边的权值进行从小到大排序,每次从剩余的边中选择权值较小且边的两个顶点不在同一个集合内的边(即不会产生回路的边),加入到生成树中,直到加入了n-1条边为止。
Kruskal算法模板题:http://hihocoder.com/problemset/problem/1098
AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<string.h> 3 struct edge 4 { 5 int u,v,w; 6 }; 7 struct edge e[1000010]; 8 #include<algorithm> 9 using namespace std; 10 bool cmp(struct edge x,struct edge y) 11 {return x.w<y.w;} 12 int f[100010]; 13 int merge(int v,int u); 14 int getf(int v); 15 int main() 16 { 17 int n,m,i,sum,c; 18 while(scanf("%d%d",&n,&m) != EOF) 19 { 20 for(i=1;i<=m;i++) 21 scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); 22 sort(e+1,e+m+1,cmp); 23 for(i=1;i<=n;i++) //初始化并查集 24 f[i]=i; 25 sum=0; 26 c=0; 27 for(i=1;i<=m;i++) //遍历每条边 28 { 29 if( merge(e[i].u,e[i].v) ) //判断是否能够构成回路 30 { 31 c++; 32 sum += e[i].w; 33 } 34 if(c==n-1) 35 break; 36 } 37 printf("%d ",sum); 38 } 39 return 0; 40 } 41 int getf(int v) 42 { 43 return f[v]==v ? v : f[v]=getf(f[v]); 44 } 45 int merge(int v,int u) 46 { 47 int t1,t2; 48 t1=getf(v); 49 t2=getf(u); 50 if(t1 != t2) 51 { 52 f[t2]=t1; //收录 53 return 1; //返回1表示不能构成回路,可以建造这条路 54 } 55 return 0; //返回0表示能构成回路,不可以建造这条路 56 }
上述算法的时间复杂度为O(MlogM),综合来说效率还是很高的,适用于稀疏图。
最后,总的来说最小生成树中Kruskal算法还是较优的,应优先使用。