转自:http://www.csie.ntnu.edu.tw/~u91029/SpanningTree.html
先收藏了,以后好好看一下。
Spanning Tree / Spanning Forest
中譯「生成樹」,從一張圖上分離出一棵包含圖上所有點的樹,便是這張圖的生成樹。一張圖的生成樹可能會有很多種。
當一張圖完全連通,必然有生成樹。當一張圖有部份不連通,則沒有生成樹,而是許多棵「生成子樹」所構成的「生成森林」。宛如 DFS tree 與 DFS forest 的關係。
生成樹也可以有權重。當圖上每條邊都有權重時,生成樹的權重為樹上每條邊的權重總和。
Minimum Spanning Tree
中譯「最小生成樹」。權重最小的生成樹就是最小生成樹。一張圖的最小生成樹可能會有很多種。
Minimum Spanning Tree:
Kruskal's Algorithm
程度★ 難度★★
用途
求出無向圖的其中一棵最小(大)生成樹。若是圖不連通,則是求出其中一叢最小(大)生成森林。
演算法
一、兩棵 MST ,要合併成一棵 MST 時,以兩棵 MST 之間權重最小的邊進行連結,當然會是最好的。
二、三棵 MST ,要合併成一棵 MST 時,先連結其中兩棵連結權重最小的 MST ,然後才連結第三棵,總是比較好。
三、一個單獨的點,可以視作一棵 MST 。
由以上三點,可以歸納出一個 greedy 演算法:以權重最小的邊連結各棵 MST ,一定比較好。
一、一開始圖上每一個點,各自是一棵最小生成子樹MSS。 二、圖上所有邊,依照權重大小,由小到大排序。 三、依序嘗試圖上所有邊,作為最小生成樹(森林)上的邊: 甲、兩端點分別位於兩棵MSS,也就是產生了橋: 用這條邊連接兩棵MSS,合併成一棵MSS。 這條邊會是最小生成樹(森林)上的邊。 乙、兩端點皆位於同一棵MSS,也就是產生了環: 捨棄這條邊。
每次選中的邊,都是 MST 上的邊。沒有選中的邊,不論這張圖以後又增加了多少邊,絕不會成為 MST 上的邊。
時間複雜度
一、排序圖上所有邊,需時 O(ElogE) 。
二、連接 MSS ,一般是運用「 Disjoint-set Forest 」,需時 O(E*α(E,V)) 。
故時間複雜度為 O(ElogE) 。
- const int V = 100, E = 1000;
- struct Edge {int a, b, c;} e[E]; // edge list
- bool operator<(const Edge& e1, const Edge& e2) {return e1.c < e2.c;}
- // disjoint-sets forest
- int p[V];
- int init() {for (int i=0; i<V; ++i) p[i] = i;}
- int find(int x) {return x == p[x] ? x : (p[x] = find(p[x]));}
- void union(int x, int y) {p[find(x)] = find(y);}
- void Kruskal()
- {
- init();
- // 圖上所有邊,依照權重大小,由小到大排序。
- sort(edge, edge+E); // O(NlogN)
- // 依序找出最小生成樹上的V-1條邊。
- int i, j;
- for (i = 0, j = 0; i < V-1 && j < E; ++i)
- {
- // 產生環,則捨棄。直到產生橋。
- while (find(e[j].a) == find(e[j].b)) j++;
- // 產生橋,則以此邊連接兩棵MSS。
- union(e[j].a, e[j].b);
- // 印出最小生成樹(森林)上的邊。
- cout << "起點:" << e[j].a
- << "終點:" << e[j].b
- << "權重:" << e[j].c;
- j++; // 別忘記累計索引值。也可以寫入迴圈。
- }
- if (i == V) cout << "得到最小生成樹";
- else cout << "得到最小生成森林";
- }
迴圈的部份也可以寫成這樣。
- // 窮舉圖上所有邊,嘗試作為最小生成樹(森林)。
- for (i = 0, j = 0; i < V-1 && j < E; ++j)
- {
- // 產生環,則捨棄。
- if (find(e[j].a) == find(e[j].b)) continue;
- // 產生橋,則以此邊連接兩棵MSS。
- union(e[j].a, e[j].b);
- // 印出最小生成樹(森林)上的邊。
- cout << "起點:" << e[j].a
- << "終點:" << e[j].b
- << "權重:" << e[j].c;
- i++; // 別忘記累計索引值。不可以寫入迴圈。
- }
Minimum Spanning Tree:
Prim's Algorithm
程度★ 難度★★
用途
求出無向圖的其中一棵最小(大)生成樹。
演算法
與「 Shortest Path: Dijkstra's Algorithm 」的概念大致相同。
主要的差異是: Dijkstra's Algorithm 屢次找不在樹上、離根最近的點, Prim's Algorithm 屢次找不在樹上、離樹最近的點。
另外一個差異是:最短路徑樹擁有特定起點,而最小生成樹可以選定任何一點作為樹根。
時間複雜度
圖的資料結構為 adjacency matrix 的話,便是 O(V^2) ;圖的資料結構為 adjacency lists 的話,還是 O(V^2) 。
就和 Dijkstra's Algorithm 一樣, Prim's Algorithm 也可以使用 Fibonacci Heap 、 Priority Queue ,得到更低的時間複雜度。
- int w[9][9]; // adjacency matrix
- int d[9]; // 紀錄目前的MST到圖上各點的距離
- int parent[9]; // 紀錄各個點在MST上的父親是誰
- bool visit[9]; // 紀錄各個點是不是已在MST之中
- void prim()
- {
- for (int i=0; i<9; i++) visit[i] = false;
- for (int i=0; i<9; i++) d[i] = 1e9;
- d[0] = 0; // 可以選定任何點作為樹根,這裡以第零點作為樹根。
- parent[0] = 0;
- for (int i=0; i<9; i++)
- {
- int a = -1, b = -1, min = 1e9;
- for (int j=0; j<9; j++)
- if (!visit[j] && d[j] < min)
- {
- a = j; // 記錄這一條邊
- min = d[j];
- }
- if (a == -1) break; // 與起點相連通的MST都已找完
- visit[a] = true;
- // d[a] = 0; // 註解後,得到MST每條邊權重。
- for (b=0; b<9; b++)
- // 以下與Dijkstra's Algorithm略有不同
- if (!visit[b] && w[a][b] < d[b])
- {
- d[b] = w[a][b]; // 離樹最近,不是離根最近。
- parent[b] = a;
- }
- }
- }
UVa 10034 10147 10307 10397 10600 10842
© 2010 tkcn . All rights reserved.
節點編號 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
距離樹的距離 | 0 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ |