> 補充教材: [Graph / 圖](/JRiwv0omQ9e7JGynHgYeuQ) ## 何謂生成樹 在一張圖中,選一些邊使圖連通 如果這個子圖是一棵樹,那它就是Spanning Tree(生成樹) e.g. ![](https://i.imgur.com/JePHOym.png =500x) 這是一張圖 ![](https://i.imgur.com/Mjh5APW.png =500x) 如果我們選一些邊 ![](https://i.imgur.com/1sU6SSy.png =500x) 像這樣,是一棵樹,則這一顆就是生成樹 --- ## 何謂最小生成樹 一張邊上帶權重的圖,取一些邊使其為一生成樹且邊權總和最小 先看一張帶權重的圖 ![](https://i.imgur.com/ei6Qmu0.png =500x) 這張圖的其中一個最小生成樹如下 ![](https://i.imgur.com/VrGa5kO.png =500x) 一張圖可能不只有一個最小生成樹,以下這也是一個最小生成樹 ![](https://i.imgur.com/BcdsfN7.png =500x) --- ## 如何求最小生成樹 ### Kruskal's algorithm #### 概念 最直覺的演算法,對所有邊依照權重由小到大排序 當一個邊兩側的點不連通,就連起來並加進MST i.e. 一開始每個點都是一個集合 ![](https://i.imgur.com/6BIxvnR.png =500x) 找最小的邊,而邊的兩端點不在相同集合則合併 ![](https://i.imgur.com/B0IA40E.png =500x) 同上,合併 ![](https://i.imgur.com/zC1FpMO.png =500x) 同上 ![](https://i.imgur.com/vwp8gzA.png =500x) 接下來,遇到該邊的兩端點在相同集合,則跳過 ![](https://i.imgur.com/i2eVun3.png =500x) 繼續找最小邊合併 ![](https://i.imgur.com/VIE5KnA.png =500x) 最後會把所有邊遍歷完,用實線連起來的邊即為MST ![](https://i.imgur.com/L8IM7OU.png =500x) 想法很簡單,但需要一個可以快速實現判斷連通與否的資料結構 > [Disjoint Set Union (DSU) / 互斥集](/ktXqlbtaSpGoxGH6MNef-w) 有了這個資料結構,接下來的實作就很簡單了 #### 範例程式碼 ```cpp= inline void solve() { //caculate MST weight sum int n,m; while(cin>>n>>m){ vector<edge> e(m,edge(0,0,0)); for(auto& i:e){ int a,b,w; cin>>a>>b>>w; i=edge(a,b,w); } sort(e.begin(),e.end()); vector<int> dsu(n); iota(dsu.begin(),dsu.end(),0); F<int(int)> find=[&](int x){ return x==dsu[x]?x:dsu[x]=find(dsu[x]); }; auto join=[&](int a,int b){ if(find(a)==find(b)) return false; dsu[find(a)]=dsu[find(b)]; return true; }; long long sum=0; for(const auto& i:e) if(join(i.a,i.b)) sum+=i.w; int isconnected=true; for(int i=0;i!=n;i++) if(find(0)!=find(i)) isconnected=false; cout<<(isconnected?sum:-1)<<endl; } } ``` --- ### Prim's algorithm #### 概念 和 [Kruskal's algorithm](#Kruskal’s-algorithm) 不一樣的是 [Prim's algorithm](#Prim's-algorithm) 是由點的想法出發 先任意找一個點,從該點延伸出的邊中取權重最小者,再從該點延伸重複做以上操作,可以使用 `priority_queue` 維護並快速取出邊權最小者,直到連接中的點皆已被拜訪為止,最後判斷點的數量是否為邊的數量加一。 i.e. 一開始先指定任一點當起始點 ![](https://i.imgur.com/LS3gQQT.png =500x) 延伸出去,將出邊放進`priority_queue`,並取最小者 ![](https://i.imgur.com/6hGCK8Z.png =500x) 繼續延伸,選最小邊 ![](https://i.imgur.com/HU1UyVV.png =500x) 同上 ![](https://i.imgur.com/qtEhlsO.png =500x) 同上,所有點遍歷完成 ![](https://i.imgur.com/K0LbiZP.png =500x) #### 範例程式碼 (GT) ```cpp= vector<pii> g[N]; vector<bool> v; ll prim(int n, int m) { // n vertices, m edges v.assign(n, 0); for (int i = 0; i < n; g[i].clear(), i++); for (int i = 0, a, b, c; i < m; i++) cin >> a >> b >> c, g[a].eb(b, c), g[b].eb(a, c); // pq : {first, second}->{edge cost, vertex id} priority_queue<pii, vector<pii>, greater<pii>> pq; pq.ep(0, 0); ll sum = 0; int cnt = 0; while (!pq.empty()) { auto a = pq.top(); pq.pop(); if (v[a.s]) continue; v[a.s] = 1, cnt++, sum += a.f; for (const auto& i : g[a.s]) if (!v[i.f] && i.s != a.s) pq.ep(i.s, i.f); } return (cnt == n ? sum : -1); } ``` #### 範例程式碼 (Konchin) ```cpp= inline void solve() { //caculate MST weight sum int n,m; while(cin>>n>>m){ vector<vector<edge>> e(n); for(int i=0;i!=m;i++){ int a,b,w; cin>>a>>b>>w; e[a].emplace_back(b,w); e[b].emplace_back(a,w); } priority_queue<edge> pq; vector<bool> visited(n,false); pq.emplace(0, 0); long long sum=0; for(int i=0;i!=n;i++){ //at most n times (totally n vertices) int a=~0u; //-1 while(pq.size()&&visited[a=pq.top().b]) pq.pop(); if(!~a) break; else sum+=pq.top().w,visited[a]=true; for(const auto& i:e[a]) if(!visited[i.b]) pq.emplace(i); } cout<<(*min_element(visited.begin(),visited.end())?sum:-1)<<endl; } } ``` ---