最小生成树相关算法总结
最小生成树应该是我们相当熟悉的东西了。对于一个连通的无向图G,G中权值最小的生成树称为最小生成树。这是最小生成树的定义,在这片文章里我会把最近学到的关于最小生成树及其相关的算法做一个总结和分享吧, 并会把我整理的模版贴出来。
对于最基本的的最小生成树问题我们可以使用kruskal算法和prime算法(前者适用于稀疏图)在O(mlogm)与O(nlogn+m)时间内解决。关于这两种最基本的算法我就不在这里多说了。
一、最小瓶颈路问题
在最小生成树的实际应用中我们常常会遇到这一类问题,给你一张无向带权连通图和两个节点u,v让你求u,v之间的一条路径使得u->v路径上最大的边权最小值。这一类问题我们称之为最小瓶颈路问题。
对于这种问我们分两种讨论:
(1)单个询问
首先,我们从最简单的一个询问来说。我们很容易就能想到对于这一类问题我们可以先求出最小生成树然后在树上执行一次dfs然后就能求出答案了,我想这种应该是最简单的做发复杂度是O(mlogm)能够满足题目的需求。复杂度是不能优化了,但我们可不可以减少一下代码的长度呢,当然可以在这里我引用郭华阳在2007年发表的国家集训队论文里的算法。
首先我们看如下过程:
这几张ppt一看很清楚了吧。下面有一道基础的题目可以练一下uva 534:http://www.cnblogs.com/shu-xiaohao/p/3531626.html
(2)多个询问
单个询问非常的简单但是对于多组询问上述解法就没有用武之地了。那这时候我们怎么处理呢?利用倍增法处理,下面列出算法步骤:
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
二、次小生成树问题
另一类相关问题就是次小生成树了。题目的要求就是求出权值第二小的最小生成树的值(若最小有两种方案则值相等),对于次小生成树都有一种暴力做法就是枚举所有不在最小生成树中的边这个能求出答案但是复杂度太高往往无法承受,下面给出一种 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汪汀国家队论文)。感觉这些都比较难等以后掌握好了在写总结吧。这里的算法大多十分简单。由于水平有限,若发现问题欢迎留言一定及时更正!