问题来源:hdu-1233
Problem Description
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。Input
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
当N为0时,输入结束,该用例不被处理。
Output
对每个测试用例,在1行里输出最小的公路总长度。
Sample Input
3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0
Sample Output
3
5
源代码_Prim算法:
#include<stdio.h>
#include<string.h>
#define INF 999999999
int Prim_MST( int N , int city[][100] ); //普利姆算法求最小生成树
int main( ){
int N , city[100][100]; //city存储地图,city[i][j]表示从i城市到j城市的距离
int a , b , c;
while( ~scanf("%d",&N) && N ){ //图的初始化
for( int i=1 ; i<=N ; i++ )
for( int j=1 ; j<=N ; j++ ){
city[i][j] = INF; //不能到达
city[i][i] = 0; //能到达(回路)
}
for( int i=1 ; i<=N*(N-1)/2 ; i++ ){
scanf("%d%d%d",&a,&b,&c);
city[a][b] = city[b][a] = c; //双向图
}
printf("%d\n",Prim_MST( N , city ));
}
return 0;
}
int Prim_MST( int N , int city[][100] ){
int i , j , k , min , cost=0 , num=0;
int dis[100] , mark[100];
//dis[i]是i城市到加入到生成树的结点中最小的距离,随着城市的加入会被更新
//mark[i]是标志i城市是否加入到了最小生成树中
memset( mark , false , sizeof( mark ) );
for( int i=1 ; i<=N ; i++ )
dis[i] = city[1][i]; //dis[i]的初始化
mark[1] = true;
num++;
while( num < N ){ //生成树的点总数量没达到N就继续生成
for( i=1 , min=INF ; i<=N ; i++ ){ //寻找没在U几何里的最小距离点
if( mark[i]==false && dis[i]<min ){
min = dis[i];
k = i;
}
}
mark[k] = true; //标志该城市加入到了U集合里
cost += min;
num++;
for( j=1 ; j<=N ; j++ ){ //更新其他的dis值
if( mark[j]==false && city[k][j]<dis[j] ){
dis[j] = city[k][j];
}
}
}
return cost;
}
源代码_Kruskal:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct Edge{ //定义边结构,成员是起点终点和长度
int v1;
int v2;
int len;
};
int parent[100]; //双亲结点,用来判断是否属于一个几何
Edge edge[5000];
int Kruskal_MST( int N );
int Root( int k );
void Merge( int k1 , int k2 );
int cmp( const void *a , const void *b ){ return ((Edge *)a)->len - ((Edge *)b)->len; }
int main( ){
int N;
while( ~scanf("%d",&N) && N ){
for( int i=1 ; i<=N*(N-1)/2 ; i++ )
scanf("%d%d%d",&edge[i].v1,&edge[i].v2,&edge[i].len);
printf("%d\n",Kruskal_MST( N ));
}
return 0;
}
int Kruskal_MST( int N ){
int cost=0;
memset( parent , -1 , sizeof(parent) ); //初始化parent数组
qsort( &edge[1] , N*(N-1)/2 , sizeof(edge[1]) , cmp ); //按照边的权值进行非递增排序
for( int i=1 ; i<=N*(N-1)/2 ; i++ ){
if( Root(edge[i].v1) != Root(edge[i].v2) ){ //两个点不属于一个集合
Merge( edge[i].v1 , edge[i].v2 ); //将其连接起来属于一个集合
cost += edge[i].len;
}
}
return cost;
}
int Root( int k ){ //寻找k的祖先
if( parent[k]<0 ) //达到搜索深度
return k;
return parent[k]=Root(parent[k]); //优化,使k的paren就为Root(k),从树的结构上看,路径已经压缩短了
}
void Merge( int k1 , int k2 ){
int K1 , K2;
K1 = Root( k1 ); //K1为k1的祖先
K2 = Root( k2 ); //K2为k2的祖先
if( parent[K1] > parent[K2] ){ //优化,把子孙结点个数少的那个祖先接在子孙结点个数多的祖先下方
parent[K2] += parent[K1]; //祖先的parent一定为负数,其绝对值为子孙结点的个数
parent[K1] = K2;
}
else{
parent[K1] += parent[K2];
parent[K2] = K1;
}
}
算法分析:Prim算法主要以点的角度来解决MST(Minimum spaning tree),Kruskal主要以边的角度来解决MST,所以说对于稀疏图(边数较少)适合采用Kruskal算法,稠密图(边数较多)适合采用Prim算法。Prim算法一般用邻接矩阵存储图,且有dis数组存储未生成的点到生成点的最小路径值;Kruskal一般用结构数组来存储边集合,其中Root和Merge函数均有优化(路径压缩)。