克魯斯卡爾求最小生成樹
阿新 • • 發佈:2018-10-21
角度 ems 不理解 += 重復 highlight 賦值 truct bool
處理何種問題:求解最小生成樹,適合點多邊少的無向圖。(以證明,放心用)
性能:時間復雜度為O(e*loge),e為邊的個數。
原理:貪心策略
實現步驟:
<1>設一個有n個頂點的聯通網絡為G(V,E),最初先構造一個只有n個頂點,沒有邊的非連通圖T={V,空},圖中的每一個頂點自成一個連通分量。
<2>在E中選擇一條具有最小權值的邊時,若該邊的兩個頂點落在不同的連通分量上,則將此邊加入到T中;否則,即這條邊的兩個頂點屬於同一個連通分量,將此邊舍去(此後永不選用這條邊),重新選擇一條權值最小的邊。
<3>如此重復下去,直至選了(n-1)條有效邊,或者所有的頂點都在同一連通分量上為止。
備註:在此之前,本人對於網上給出的對於克魯斯卡爾的證明並不理解,只覺得這種貪心策略有Bug,這次換一個角度去理解這個算法:<1>一個孤立的點V要想連入最後的那個生成樹中,只需要找到在邊的中找連接V,且權值最小的那條邊即可,則可證明,權值最小的那條邊必然在最後的最小生成樹中;<2>對於已經連線的兩個孤立點,可看作一個點,他們共享互相邊的性質。<3>一顆生成樹有且僅有n-1條邊。根據以上三點,即可推出克魯斯卡爾的正確性。只是在實現方式上有些難以理解。
輸入樣例解釋:
4 6//頂點的個數,邊的個數
1 2 1//一條邊的兩個頂點和該邊上的權值
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
輸出樣例解釋:
5//該無向圖的最小生成樹
代碼
#include<iostream> #include<cstdio> #include<string.h> #include<algorithm> using namespace std; const int MaxN=510;///最大頂點的數 const int MaxM=251000;///最大邊數 int F[MaxN];///並查集 struct Edge { int u,v,w; }; Edge edge[MaxM];///儲存邊的信信息,包括起點/終點/權值 int tal;///邊數,加邊前賦值為0 void addedge(int u,int v,int w) { edge[tal].u=u; edge[tal].v=v; edge[tal].w=w; ++tal; } bool cmp(Edge a,Edge b)///排序函數,邊按照權值從小到大排序 { return a.w<b.w; } int Find(int x) { if(F[x]==-1) return x; else return F[x]=Find(F[x]); } int Kruskal(int n)///傳入點數,返回最小生成樹的權值,如果不連通則返回-1 { memset(F,-1,sizeof(F)); sort(edge,edge+tal,cmp); int cnt=0;///計算加入的邊數 int ans=0; int u,v,w,t1,t2; for(int i=0;i<tal;++i) { u=edge[i].u; v=edge[i].v; w=edge[i].w; t1=Find(u); t2=Find(v); if(t1!=t2) { ans+=w; F[t1]=t2; cnt++; } if(cnt==n-1) break; } if(cnt<n-1) return -1;///不連通 else return ans; } int main() { int n,m; while(~scanf("%d%d",&n,&m)) { tal=0;///必須是0 int u,v,w; for(int i=0;i<m;++i) { scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); } printf("%d\n",Kruskal(n)); } return 0; } /** 4 6 1 2 1 1 3 4 1 4 1 2 3 3 2 4 2 3 4 5 5 */
克魯斯卡爾求最小生成樹