咱们先来回想一下生成树是如何定义的,生成树就是用n - 1条边将图中的全部n个顶点都连通为一个连通份量,这样的边连成子树称为生成树。算法
最小生成树很明显就是生成树中权值最小的生成树,那么咱们即将要学的次小生成树或者K小生成树是怎么定义的呢,很明显就是生成树中权值第k小的生成树。数组
下面给出刘老师书中对次小生成树的定义,我是用本身的话描述的。安全
对于一个无向图G(V, E),其定义了边权为W(u, v),若T为他的一颗最小生成树,那么咱们假设存在一颗生成树T1,不存在任意一颗G的生成树T2知足W(T) <= W(T2) < W(T1),那么咱们就称T1为spa
G的次小生成树。code
咱们知道最小生成树咱们是经过Prim和Kruskal这样的贪心算法求得的,那么次小生成树咱们只是对这两种算法进行咱们须要的修改就能够进行次小生成树的求解。blog
咱们很容易能够想到最小生成树和次小生成树应该是有联系的,那么是如何联系的呢?次小生成树就是图G的全部生成树中权值第二小的生成树,也就是说咱们只须要替换最小生成树的一条边(u, v)排序
就能够获得次小生成树,显然这条边确定不能够属于原最小生成树,若是咱们将一条不属于原最小生成树的边(u, v)加入T,那么此时T中就造成了一个环,咱们在环中选一个除(u, v)权值最大的边进行删除博客
(想一下为何是选择权值最大的那条边),获得的树依然是一颗图G的生成树,咱们将全部边逐个加入原最小生成树T,得出并记录全部的生成树的权值T1,那么最后T1中最小的那个值便是次小生成树的string
权值。it
下面只对与求解最小生成树中不一样的部分进行说明:
咱们知道Prim算法是以给定的任意点做为起始点运用必定的方法对全部点进行贪心处理,缩点从而生成一颗最小生成树,那咱们只须要用数组用来描述最小生成树中每条边的访问状况以及最小
生成树中每两个顶点之间的最大边权还须要保存最小生成树中每一个顶点的父亲顶点,从而就能够方便用于计算次小生成树。
初始化:初始化全部点( i )距离最小生成树子树的距离为cost[source][ i ],全部边初始化为未访问,全部顶点之间的最大边权初始化为0。
加边:每次加入一条安全边(这里不对安全边进行解释,有不了解的能够查阅博主的上一篇博客),并将最小生成子树中顶点之间的最大边权进行更新,接着更新lowc便可。
求解次小生成树:咱们逐一枚举出全部不属于最小生成树的边(u, v),而且用w(u, v)来替代最大边权和Max(u, v),怎么个替代法?
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。
OK?
参考代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn = 1000 + 10, INF = 0x3f3f3f3f; 7 int n, m, lowc[maxn], pre[maxn], Max[maxn][maxn], cost[maxn][maxn]; 8 bool vis[maxn], used[maxn][maxn]; 9 10 int Prim() { 11 int ans = 0; 12 memset(vis, false, sizeof vis); 13 memset(Max, 0, sizeof Max); 14 memset(used, false, sizeof used); 15 vis[1] = true; 16 pre[1] = -1; 17 for(int i = 2; i <= n; i ++) { 18 lowc[i] = cost[1][i]; 19 pre[i] = 1; 20 } 21 lowc[1] = 0; 22 for(int i = 2; i <= n; i ++) { 23 int MIN = INF, p = -1; 24 for(int j = 1; j <= n; j ++) { 25 if(!vis[j] && MIN > lowc[j]) { 26 MIN = lowc[j]; 27 p = j; 28 } 29 } 30 if(MIN == INF) return -1; 31 ans += MIN; 32 vis[p] = true; 33 used[p][pre[p]] = used[pre[p]][p] = true; 34 for(int j = 1; j <= n; j ++) { 35 if(vis[j] && j != p) Max[j][p] = Max[p][j] = max(Max[j][pre[p]], lowc[p]); 36 if(!vis[j] && lowc[j] > cost[p][j]) { 37 lowc[j] = cost[p][j]; 38 pre[j] = p; 39 } 40 } 41 } 42 return ans; 43 } 44 45 int Second_Prim(int MST) { 46 int ans = INF; 47 for(int i = 1; i <= n; i ++) 48 for(int j = i + 1; j <= n; j ++) 49 if(!used[i][j] && cost[i][j] != INF) ans = min(ans, MST - Max[i][j] + cost[i][j]); 50 return ans; 51 } 52 53 int main() { 54 int t, a, b, c; 55 scanf("%d", &t); 56 while(t --) { 57 scanf("%d %d", &n, &m); 58 for(int i = 0; i <= n; i ++) 59 for(int j = 0; j <= n; j ++) 60 cost[i][j] = INF; 61 for(int i = 1; i <= m; i ++) { 62 scanf("%d %d %d", &a, &b, &c); 63 cost[a][b] = cost[b][a] = c; 64 } 65 int MST = Prim(); 66 int Second_MST = Second_Prim(MST); 67 printf("%d\n", Second_MST); 68 } 69 return 0; 70 }
Kruskal:
Kruskal算法是将图G的全部边进行排序,从小到大知足边的两个顶点有一个不在subMST中就将其加入MST,在求解次小生成树问题时咱们也须要记录MST中结点的链接状况,以及MST中两个顶点
间的最大边权。
具体操做:
初始化:初始化并查集,初始化在subMST中每一个结点 i 直接或者间接相连的边为i。
加边:每次加入一条边时,咱们更新subMST中全部与u, v相连的结点间的最大边权,接着将全部与结点v相连的边都与结点u也连起来就好了(前提是在合并时head[ head[ v ] ] = head[ u ])。
求解次小生成树:
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。滑稽.jpg
参考代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 1000 + 10, maxe = 1000 * 1000 / 2 + 5, INF = 0x3f3f3f3f; 8 int n, m, pre[maxn], head[maxn], Max[maxn][maxn]; 9 struct Edge { 10 int u, v, w; 11 bool vis; 12 }edge[maxe]; 13 vector<int> G[maxn]; 14 15 bool cmp(const Edge &a, const Edge &b) { 16 return a.w < b.w; 17 } 18 19 void Init() { 20 for(int i = 1; i <= n; i ++) { 21 G[i].clear(); 22 G[i].push_back(i); 23 head[i] = i; 24 } 25 } 26 27 int Find(int x) { 28 if(head[x] == x) return x; 29 return head[x] = Find(head[x]); 30 } 31 32 int Kruskal() { 33 sort(edge + 1, edge + 1 + m, cmp); 34 Init(); 35 int ans = 0, cnt = 0; 36 for(int i = 1; i <= m; i ++) { 37 if(cnt == n - 1) break; 38 int fx = Find(edge[i].u), fy = Find(edge[i].v); 39 if(fx != fy) { 40 cnt ++; 41 edge[i].vis = true; 42 ans += edge[i].w; 43 int len_fx = G[fx].size(), len_fy = G[fy].size(); 44 for(int j = 0; j < len_fx; j ++) 45 for(int k = 0; k < len_fy; k ++) 46 Max[G[fx][j]][G[fy][k]] = Max[G[fy][k]][G[fx][j]] = edge[i].w; 47 head[fx] = fy; 48 for(int j = 0; j < len_fx; j ++) 49 G[fy].push_back(G[fx][j]); 50 } 51 } 52 return ans; 53 } 54 55 int Second_Kruskal(int MST) { 56 int ans = INF; 57 for(int i = 1; i <= m; i ++) 58 if(!edge[i].vis) 59 ans = min(ans, MST + edge[i].w - Max[edge[i].u][edge[i].v]); 60 return ans; 61 } 62 63 int main() { 64 int t; 65 scanf("%d", &t); 66 while(t --) { 67 scanf("%d %d", &n, &m); 68 for(int i = 1; i <= m; i ++) { 69 scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w); 70 edge[i].vis = false; 71 } 72 int MST = Kruskal(); 73 int Second_MST = Second_Kruskal(MST); 74 printf("%d\n", Second_MST ); 75 } 76 return 0; 77 }