标签:
本文链接:http://www.cnblogs.com/Ash-ly/p/5494975.html
定义:
设G = (V, E)是连通的无向图,T是图G的一个最小生成树.如果有另外一棵树T1,T1 ≠ T,满足不存在树T‘,T‘ ≠ T,w(T‘) < w(T1),则称T1是图G的次小生成树.
算法:
1:基本算法
最简单也最容易想到的是,设T是G的最小生成树,依次枚举T的边并去掉,再求最小生成树,所得到的这些值的最小值就是次小生成树,由于最小生成树有N-1条边,这种方法就相当于运行了N次最小生成树的算法,算法的时间复杂度比较高.大约是N * M的数量级.
2:算法二
定义由最小生成树T进行一次可行变化得到的新的生成树所组成的集合,称为树T的邻集,记为N(T).所谓可行变化即去掉T中的一条边,再新加入G中的一条边,使得新生成的图仍为树.设T是图G的最小生成树,如果T1满足w(T1) = min{ w(T‘) | T‘ E N(T)},则T1是图G的次小生成树.
定理:如果图G的边的个数E和个点的个数N不满足关系E + 1 = N,那么存在边(u,v) 属于 T 和(x, y)不属于T满足T \ (u, v) U (x, y)是图的一颗次小生成树.
既然存在边(u, v) 属于 T 和(x, y) 不属于T满足T \ (u, v) U (x, y)是图的一颗次小生成树,那么所有的T \ (u, v) U (x, y)刚好构成了T的邻集,则T的邻集中权值最小的就是次小生成树了,但是如果每次枚举(u, v)和(x, y),还要判断能否构成一棵树,复杂度太高.那么这里应该这么做,先加入(x,y),对于一棵树加入(x, y)后一定会成环,如果删去环上除(x ,y)以外权值最大的一条边,会得到加入(x,y)时权值最小的边.那么接下来的问题就是如何快速求得这个环上权值最大边了.最小生成树中x到y的最长边可以使用树形动态规划或者LCA等方法在O(N2)的时间复杂度内计算出来.但是如果使用的是Kruskal算法求最小生成树,可以在算法的运行过程中求出x到y路径上的最长边,因为每次合并两个等价类的时候,分别属于两个等价类的每两个点之间的最长边一定是当前加入的边,按照这条性质进行记录的话就可以求出来最小生成树中在每两个点的路径上的最长边.
算法实现上,首先用求最小生成树的算法求出其权值之和为mst,然后枚举不属于最小生成树的边(x, y),并添加到最小生成树中,那么树必定形成环,然后删掉这个环内(不包含(x,y))最长的边.然后计算权值之和,枚举所有不属于最小生成树的边,取其权值的最小值,就是次小生成树.
下面用图来说明下计算(x,y)之间的最长边:
比如利用kruskal走到了上述步骤,接下来要添加边(V2, V5),且权值为8,那么分别属于两个等价类<V5, V3> 和 <V1, V2, V4, V6>的两个点之间的距离应该都更新为8,即length[3][2] = length[3][1] = length[3][4] = length[3][6] = 8, length[5][2] = length[5][1] = length[5][4] = length[5][6] = 8,每增加一条边,就如此的更新下去,最后就可以得到图中两个节点之间的路径上的最长距离.
下面再用图简单阐述下算法:
假设上图是图的最小生成树,并存在边(v5, v6),且其权值为8,那么当枚举到此条边时,在图中添加边(v5,v6),图中便有了一个环,在此环中找到除新加边以外最大的边的权值为7,那么删除此边,计算权值,然后继续枚举其他不存在于最小生成树的边,从中取得最小值,就是要求的答案.
代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 #include <algorithm> 5 #include <cstdio> 6 #include <string> 7 8 using namespace std; 9 typedef long long LL; 10 11 const int MAXN = 500; 12 const int MAXE = 500 * 500; 13 const int INF = 0x3f3f3f3f; 14 int pre[MAXN + 7]; 15 16 void initPre(int n){ for(int i = 0; i <= n; i++) pre[i] = i; } 17 18 //并查集 19 int Find(int x){ return x == pre[x] ? x : pre[x] = Find(pre[x]); } 20 21 void merge(int x, int y){ int fx = Find(x), fy = Find(y); if(fx != fy) pre[fx] = fy; } 22 23 struct Edge{ //前向星存边 24 int u, v; //起点 终点 25 int w; 26 bool select; 27 }edge[MAXE + 7]; 28 29 bool cmp(Edge a, Edge b){ 30 if(a.w != b.w) return a.w < b.w; 31 if(a.u != b.u) return a.u < b.u; 32 return a.v < b.v; 33 } 34 35 struct Node{//链式前向星 用于存储每个集合里面的边 36 int to; 37 int next; 38 }link[MAXN + 7]; 39 40 int head[MAXN + 7];//邻接表的头结点的位置 41 int End[MAXN + 7];//邻接表的尾节点的位置 42 int length[MAXN + 7][MAXN + 7];//最小生成树中任意两点路径上的最长边 43 44 int kruskal(int n, int m){ 45 //初始化邻接表,对于每一个顶点添加一个指向自身的边,表示以i为代表元的集合中只有点i 46 for(int i = 1; i <= n; i++){ 47 link[i].to = i, link[i].next = head[i]; 48 End[i] = i, head[i] = i; 49 } 50 sort(edge + 1, edge + 1 + m, cmp); 51 int cnt = 0; 52 for(int i = 1; i <= m; i++){ 53 if(cnt == n - 1) break;//当找到的边数等于节点数-1,说明mst已经找到 54 int fx = Find(edge[i].u); 55 int fy = Find(edge[i].v); 56 if(fx != fy){ 57 for(int j = head[fx]; j != -1; j = link[j].next)//修改length数组 58 for(int k = head[fy]; k != -1; k = link[k].next) 59 //每次合并两个等价类的之后,分别属于两个等价类的两个节点之间的最长边一定是当前加入的边 60 length[link[j].to][link[k].to] = length[link[k].to][link[j].to] = edge[i].w; 61 //合并邻接表 62 link[End[fy]].next = head[fx]; 63 End[fy] = End[fx]; 64 merge(fx, fy); 65 cnt++; 66 edge[i].select = true; 67 } 68 } 69 if(cnt < n - 1) return -1; 70 return 1; 71 } 72 73 int main(){ 74 //初始化建图后执行以下操作 75 int flag = kruskal(n, m); 76 int mst = 0; 77 for(int i = 1; i <= m; i++) if(edge[i].select) mst += edge[i].w;//计算出最小生成树 78 int secmst = INF; 79 //在 T/(u,v) + (x, y)中寻得次小生成树 80 for(int i = 1; i <= m; i++) if(!edge[i].select) secmst = min(secmst, mst + edge[i].w - length[edge[i].u][edge[i].v]); 81 return 0; 82 }
标签:
原文地址:http://www.cnblogs.com/Ash-ly/p/5494975.html