首先,圖論中的最小生成樹問題就是給出一個大小為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代碼:

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\n",sum); 41 } 42 return 0; 43 }
上述算法的時間復雜度是O(N*N),適用於頂點數較少的稠密圖(因為頂點數太多,一是開不了那么大的數組,二是容易超時)。如果數組開不了那么大,又想用prim算法的話,可以使用鄰接表來存儲圖,但是這里更推薦使用Kruskal算法,因為較容易實現,不局限於數組的大小,效率也很高。
再總結一下Kruskal算法,首先將邊按照邊的權值進行從小到大排序,每次從剩余的邊中選擇權值較小且邊的兩個頂點不在同一個集合內的邊(即不會產生回路的邊),加入到生成樹中,直到加入了n-1條邊為止。
Kruskal算法模板題:http://hihocoder.com/problemset/problem/1098
AC代碼:

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\n",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算法還是較優的,應優先使用。