最小生成树-Prim及Kruskal

问题来源: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函数均有优化(路径压缩)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章