最小生成樹問題的引入:
對於一個無向圖G(V, E),需要用圖中的n - 1條邊連接圖中的n個頂點並且不產生回路所產生的樹就叫做生成樹,其中權值總和最小的就是最小生成樹。
如何求解最小生成樹問題:
譬如給定一個無向圖G(V, E),要如何求出這個圖的一個最小生成樹呢?
下面我們先給出這個問題的一個總的解決方法:
我們先假設集合A為滿足A為圖G的最小生成樹的邊的集合。
條件P:A為G的最小生成樹的子集。
起初 : 我們設定A為空集,顯然此時集合A滿足條件P。
添加邊:每次我們找到一條邊(u, v),使得A U { (u, v) } 這個集合依然滿足條件P,我們找到的這條邊(u, v)被稱為集合A的安全邊。
以上就是最小生成樹的求解過程,我們可以很容易看出這個求解操作中的關鍵部分就是如何找出符合條件的安全邊。那么下面我們先給出求解最小生成樹問題的偽代碼。
GENERIC-MST(G, w) A <- NULL Set while(A does not form a spanning tree) find an edge(u, v) that is safe for A A = A U {(u, v)} return A
Kruskal:
Kruskal算法思想:任意時刻的中間結果是一個森林。初始n個點的集合,每次選擇權重最小且不會產生圈的邊加入集合A,合並兩個森林。為了方便檢測環和加邊,需要借助並查集。
Kruskal加邊操作:貪心思想,每次擇最優,保證不成環即可。
Kruskal具體操作:
初始化:每個頂點獨立成為一個森林。
加邊:在所有邊中選擇一條權重最小的邊用來連接兩個森林,所選擇的這條邊需要保證合並森林不會生成環。
下面給出Kruskal算法的偽代碼:
MST-KRUSKAL(G, w) A <- NULL Set for each vertex v Make_Set(v) sort the edges of G.E into nondecreasing order by weight w for each edge(u, v) taken in nondecreasing order by weight if(Find(u) != Find(v)) A = A U {(u, v)} Union(u, v) return A
Kruskal算法參考代碼:
1 typedef pair<int, int> pii; 2 const int maxn = 1000 + 5, maxe = 2000 + 5; 3 int n, e;//全局變量,圖中的點和邊的數目 4 struct Edge { 5 int x, y, cost; 6 } edge[maxe];//全局變量,edge[i]表示第i條邊的信息 7 int ans = 0;//表示為最小生成樹的邊權和 8 int num = 0;//表示所求出的邊的數量 9 pii spanning[maxn];//存儲最小生成樹的每條邊 10 int head[maxn], Rank[maxn]; 11 12 void Make_Set(int n) { 13 for(int i = 1; i <= n; i ++) { 14 Rank[i] = 0; 15 head[i] = i; 16 } 17 } 18 19 int Find(int u) { 20 if(u == head[u]) return u; 21 else return head[u] = Find(head[u]); 22 } 23 24 void Union(int u, int v) { 25 int hu = Find(u), hv = Find(v); 26 if(Rank[hu] > Rank[hv]) 27 head[hv] = hu; 28 else { 29 head[hu] = hv; 30 if(Rank[hu] == Rank[hv]) Rank[hv] += 1; 31 } 32 } 33 34 bool Is_same(int u, int v) { 35 return Find(u) == Find(v); 36 } 37 38 bool cmp(Edge a, Edge b) { 39 return a.cost < b.cost; 40 } 41 42 int Kruskal() { 43 sort(edge + 1, edge + e + 1, cmp); 44 int cnt = n; 45 Make_Set(n); 46 for(int i = 1; i <= e; i ++) { 47 if(!Is_same(edge[i].x, edge[i].y)) { 48 Union(edge[i].x, edge[i].y); 49 ans += edge[i].cost; 50 spanning[++num].first = edge[i].x; 51 spanning[num].second = edge[i].y; 52 if(cnt == 1) break;//若只剩下一個連通塊即最小生成樹已經生成,則退出 53 } 54 } 55 return ans; 56 }
Prim:
Prim算法思想:任意時刻的中間結果都是一顆最小生成樹的子樹。從指定的一個點開始,每次都花最少的代價,用一條邊把一個不在樹中的結點加進來。為了方便選擇離樹最近的點,需要借助堆。
Prim加邊操作:貪心策略,每次選擇一條邊(u, v)保證u已經是最小生成樹子樹內的結點,v是最小生成樹子樹內的所有結點u出發的邊的tail結點,每次選擇保證邊(u, v)的權重最小。
Prim算法具體操作:
初始化:將初始點標記。
加邊:每次找一條最短的兩端分別為標記和未標記的邊加入最小生成樹子樹並把未標記的點標記上。即每次加入一條安全邊,每次擴展一個點由未標記為變已標記,
直至擴展至n個點。
下面給出Prim算法的偽代碼:
MST-PRIM(G, w, source) for each vertex u u:key <- Infinite //表示距離結點u最近的結點到u的距離 u.head <- NULL//表示距離結點u最近的結點的編號 source:key <- 0 Q <- G.Vertex // the source vertex while(Q != NULLSet) u <- EXTRACT-MIN(Q)//Find and Delete for each v belong to G.Adj[u] if(v belong to Q and w(u, v) < v.key) v.head <- u; v.key <- w(u, v);
Q <- v
Prim算法實現代碼:
1 #include <cstdio> 2 #include <cstring> 3 #include <queue> 4 #include <map> 5 #include <vector> 6 using namespace std; 7 8 typedef pair<int, int> pii; 9 const int maxn = 100 + 5, maxe = 200 + 5, INF = 0x3f3f3f3f; 10 struct Edge { 11 int to, cost; 12 friend bool operator < (const Edge &a, const Edge&b) { 13 return a.cost > b.cost; 14 } 15 }; 16 vector<Edge> edge[maxe]; 17 int n, e, ans, dist[maxn]; 18 bool vis[maxn]; 19 20 void addedge(int u, int v, int w) { 21 edge[u].push_back({v, w}); 22 } 23 24 void MST_Prim(int source) { 25 memset(vis, false, sizeof vis); 26 for(int i = 2; i <= n; i ++) dist[i] = INF; 27 dist[source] = ans = 0; 28 priority_queue <Edge> Q; 29 Q.push({source, dist[source]}); 30 while(!Q.empty()) { 31 Edge u = Q.top(); 32 Q.pop(); 33 if(vis[u.to]) continue; 34 vis[u.to] = true; 35 ans += dist[u.to]; 36 for(int i = 0; i < edge[u.to].size(); i ++) { 37 Edge e = edge[u.to][i]; 38 if(dist[e.to] > e.cost) { 39 dist[e.to] = e.cost; 40 Q.push({e.to, dist[e.to]}); 41 } 42 } 43 } 44 } 45 46 int main() { 47 int x, y, w; 48 scanf("%d", &n); 49 for(int i = 1; i <= n; i ++) { 50 scanf("%d %d %d", &x, &y, &w); 51 addedge(x, y, w); 52 addedge(y, x, w); 53 } 54 MST_Prim(1); 55 for(int i = 1; i <= n; i++) edge[i].clear();//被這個坑了一天,一定要記住全局變量要清空 56 return 0; 57 }
