問題來源: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函數均有優化(路徑壓縮)。