次小生成樹(Prim + Kruaskal)

 問題引入:

  咱們先來回想一下生成樹是如何定義的,生成樹就是用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:

  咱們知道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 }
相關文章
相關標籤/搜索