最小生成樹應該是咱們至關熟悉的東西了。對於一個連通的無向圖G,G中權值最小的生成樹稱爲最小生成樹。這是最小生成樹的定義,在這片文章裏我會把最近學到的關於最小生成樹及其相關的算法作一個總結和分享吧, 並會把我整理的模版貼出來。html
對於最基本的的最小生成樹問題咱們可使用kruskal算法和prime算法(前者適用於稀疏圖)在O(mlogm)與O(nlogn+m)時間內解決。關於這兩種最基本的算法我就不在這裏多說了。算法
1、最小瓶頸路問題數組
在最小生成樹的實際應用中咱們經常會遇到這一類問題,給你一張無向帶權連通圖和兩個節點u,v讓你求u,v之間的一條路徑使得u->v路徑上最大的邊權最小值。這一類問題咱們稱之爲最小瓶頸路問題。優化
對於這種問咱們分兩種討論:ui
(1)單個詢問spa
首先,咱們從最簡單的一個詢問來講。咱們很容易就能想到對於這一類問題咱們能夠先求出最小生成樹而後在樹上執行一次dfs而後就能求出答案了,我想這種應該是最簡單的作發覆雜度是O(mlogm)可以知足題目的需求。複雜度是不能優化了,但咱們可不能夠減小一下代碼的長度呢,固然能夠在這裏我引用郭華陽在2007年發表的國家集訓隊論文裏的算法。code
首先咱們看以下過程:htm
這幾張ppt一看很清楚了吧。下面有一道基礎的題目能夠練一下uva 534:http://www.cnblogs.com/shu-xiaohao/p/3531626.htmlblog
(2)多個詢問get
單個詢問很是的簡單可是對於多組詢問上述解法就沒有用武之地了。那這時候咱們怎麼處理呢?利用倍增法處理,下面列出算法步驟:
1.求出最小生成樹
2.倍增法預處理出f數組f[i][j]表示j號結點2^i次方個父親。
3.根據每一個詢問查詢lca在過程當中統計最大的邊,即爲答案。
對於這個算法咱們要求不只掌握最小生成樹還須要會lca的倍增法。下面給出代碼:
1 const int LEN = 100000+10; 2 const int LOG_LEN = 25; 3 struct edge{int from, to;ll val;}; 4 vector<pil> Map[LEN]; 5 edge e[LEN]; 6 int n, m, fa[LEN], q, qa, qb, parent[LOG_LEN][LEN], depth[LEN]; 7 ll dis[LOG_LEN][LEN]; 8 bool cmp(edge a, edge b){return a.val<b.val;} 9 //UFSET 10 void init(){for(int i=0; i<LEN; i++)fa[i] = i;} 11 int Find(int a){return fa[a]==a?a:Find(fa[a]);} 12 void Union(int a, int b){int pa = Find(a), pb = Find(b);if(pa!=pb)fa[pa] = pb;} 13 14 //Use Kruskal to build MST 15 void Kruskal() 16 { 17 sort(e, e+m, cmp); 18 init(); 19 int cnt = 0; 20 for(int i=0; i<m; i++){ 21 int a = e[i].from, b = e[i].to; 22 ll v = e[i].val; 23 if(Find(a) == Find(b))continue; 24 Union(a, b); 25 Map[a].PB(MP(b,v)); 26 Map[b].PB(MP(a,v)); 27 cnt++; 28 if(cnt == n-1) return ; 29 } 30 } 31 32 //LCA 33 void dfs(int v, int f, int d) 34 { 35 parent[0][v] = f; 36 dis[0][v] = -1; 37 depth[v] = d; 38 for(int i=0; i<Map[v].size(); i++){ 39 pil nv = Map[v][i]; 40 if(nv.first!=f) dfs(nv.first, v, d+1); 41 else dis[0][v] = nv.second; 42 } 43 } 44 45 void init_lca() 46 { 47 memset(dis, 0, sizeof dis); 48 memset(depth, -1, sizeof depth); 49 for(int i=1; i<=n; i++)if(depth[i]<0)dfs(i, -1, 0); 50 for(int k=0; k+1<LOG_LEN; k++){ 51 for(int i=1; i<=n; i++){ 52 if(parent[k][i]<0) parent[k+1][i] = dis[k+1][i] = -1; 53 else { 54 parent[k+1][i] = parent[k][parent[k][i]]; 55 dis[k+1][i] = max(dis[k][i], dis[k][parent[k][i]]); 56 } 57 } 58 } 59 } 60 61 ll lca(int u, int v) 62 { 63 if(depth[v] > depth[u]) swap(u, v); 64 ll ret = 0; 65 for(int i=0; i<LOG_LEN; i++){ 66 if((depth[u]-depth[v]) >> i & 1) { 67 ret = max(ret, dis[i][u]); 68 u = parent[i][u]; 69 } 70 } 71 if(u==v) return ret; 72 for(int i=LOG_LEN-1; i>=0; i--){ 73 if(parent[i][u]!=parent[i][v]){ 74 ret = max(ret, dis[i][u]); 75 ret = max(ret, dis[i][v]); 76 u = parent[i][u];v = parent[i][v]; 77 } 78 } 79 ret = max(ret, dis[0][u]); 80 ret = max(ret, dis[0][v]); 81 return ret; 82 }
對於這個也有一道基礎例題uva 11354:http://www.cnblogs.com/shu-xiaohao/p/3532800.html
2、次小生成樹問題
另外一類相關問題就是次小生成樹了。題目的要求就是求出權值第二小的最小生成樹的值(若最小有兩種方案則值相等),對於次小生成樹都有一種暴力作法就是枚舉全部不在最小生成樹中的邊這個能求出答案可是複雜度過高每每沒法承受,下面給出一種 O(n^2)算法。
1.求出最小生成樹。
2.dfs出每一個點對之間的最大邊長。
3.枚舉全部不在最小生成樹上的邊設(u,v),刪除原來u,v之間最大邊長,加上這條邊的邊長,更新答案。
怎麼樣簡單吧。
給出代碼:
1 const int LEN = 101; 2 int Map[LEN][LEN]; 3 int n, m, top, fa[LEN], dis[LEN][LEN], tag[LEN*LEN], vis[LEN]; 4 struct E{ 5 int u, v, val; 6 }edge[LEN*LEN]; 7 8 bool cmp(E a, E b){return a.val < b.val;} 9 void init(){for(int i=0; i<LEN; i++) fa[i] = i;} 10 int Find(int x){return fa[x] == x ? x : fa[x] = Find(fa[x]);} 11 12 int kruskal(){ 13 int cnt = 0, ret = 0; 14 init(); 15 memset(tag, 0, sizeof tag); 16 sort(edge, edge+m, cmp); 17 for(int i=0; i<m; i++){ 18 int pa = Find(edge[i].u), pb = Find(edge[i].v); 19 if(pa == pb) continue; 20 fa[pa] = pb; 21 cnt ++; 22 Map[edge[i].u][edge[i].v] = Map[edge[i].v][edge[i].u] = edge[i].val; 23 tag[i] = 1; 24 ret += edge[i].val; 25 if(cnt == n-1) break; 26 } 27 return ret; 28 } 29 30 void dfs(int bg, int v, int val){ 31 vis[v] = 1; 32 dis[bg][v] = val; 33 for(int i=1; i<=n; i++){ 34 if(Map[v][i] != INF && vis[i] == 0) dfs(bg, i, max(val, Map[v][i])); 35 } 36 } 37 38 int main() 39 { 40 //input 41 int ans = kruskal(); 42 for(int i=1; i<=n; i++){ 43 memset(vis, 0, sizeof vis); 44 dfs(i, i, 0); 45 } 46 for(int i=0; i<m; i++){ 47 if(tag[i])continue; 48 //更新答案 49 } 50 } 51 return 0; 52 }
這個問題也是給出一道基礎例題 poj 1679:http://www.cnblogs.com/shu-xiaohao/p/3543195.html
好了差很少講完了,其實還有最小樹形圖(zhuliu算法),最小限制生成樹,k度最小生成樹(參見2004汪汀國家隊論文)。感受這些都比較難等之後掌握好了在寫總結吧。這裏的算法大多十分簡單。因爲水平有限,若發現問題歡迎留言必定及時更正!