最小生成樹-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函數均有優化(路徑壓縮)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章