次小生成樹詳解及模板(prim 以及 kruskal)
阿新 • • 發佈:2019-01-08
寫在前面
我們大部分都對最小生成樹瞭解的多一些,一般求最小生成樹的演算法是prim、kurskal,那麼對於次小生成樹,我們也可以用上面兩種演算法來求解
演算法解釋
這兩種演算法的思路都是相同的,首先求出最小生成樹,我們列舉每條不在最小生成樹上的邊,並把這條邊放到最小生成樹上面,然後就一定會形成環,那麼我們在這條環路中取出一條最長的路(除了新加入的那一條邊)。最終我們得到的權值就是次小生成樹的權值。
演算法實現
prim演算法實現:
我們在求解最小生成樹的時候我們要使用一個二位陣列maxd[i][j]表示最小生成樹中i點到j點的最遠距離,我們使用動態規劃的思想來計算這個陣列,比如當前節點為x,他的父親節點為per[x],以及根節點root,那麼
maxd[root][x] = max(maxd[root][per[x]] , maxd[per[x]][x]);
我們就會得到最終的結果陣列
我們還需要陣列:connect[i][j]表示最小生成樹中這條邊有沒有被用到,剩下的就是我們要去模擬演算法解釋裡所說的刪邊以及添邊的操作了
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> #define INF 0x3f3f3f3f using namespace std; const int MAXN = 110; int n,m,mapp[MAXN][MAXN]; bool vis[MAXN],connect[MAXN][MAXN]; int dis[MAXN],maxd[MAXN][MAXN],per[MAXN]; void Init() { memset(mapp,INF,sizeof(mapp)); memset(connect,false,sizeof(connect)); } int prim() { memset(maxd,0,sizeof(maxd)); memset(vis,false,sizeof(vis)); for(int i = 1;i <= n;i ++) { dis[i] = mapp[1][i];per[i] = 1;//首先父親節點都是根節點 } vis[1] = 1; dis[1] = 0; int res = 0; for(int i = 1;i < n;i ++) { int index = -1,temp = INF; for(int j = 1;j <= n;j ++) if(!vis[j] && dis[j] < temp) { index = j;temp = dis[j]; } if(index == -1) return res; vis[index] = 1; connect[index][per[index]] = false;connect[per[index]][index] = false;//這條邊已經在最小生成樹中,後面我們就不能新增這條邊了 res += temp; maxd[per[index]][index] =maxd[index][per[index]] = temp;//更新點之間的最大值 for(int j = 1;j <= n;j ++) { if(j != index && vis[j])//只是更新我們已經遍歷過來的節點 { maxd[index][j] = maxd[j][index] = max(maxd[j][per[index]],dis[index]); } if(!vis[j] && mapp[index][j] < dis[j]) { dis[j] = mapp[index][j]; per[j] = index; } } } return res; } int main() { int T; scanf("%d\n",&T); while(T--) { scanf("%d%d",&n,&m); Init(); int u,v,w; for(int i = 0;i < m;i ++) { scanf("%d%d%d",&u,&v,&w); mapp[u][v] = w;mapp[v][u] = w; connect[u][v] = true;connect[v][u] = true; } int ans = prim(); bool over = false; //如果有某條邊沒有被最小生成樹使用,並且i~j的最大值大於圖中得到最大值,那麼就表示次小生成樹存在 //相反就不會存在 for(int i = 1;!over && i <= n;i ++) for(int j = 1;j <= n;j ++) { if(connect[i][j] == false || mapp[i][j] == INF) continue; if(mapp[i][j] == maxd[i][j])//當邊長度相同是就是表示最小生成樹相同 { over = 1; break; } } if(over) printf("Not Unique!\n"); else printf("%d\n",ans); } //如果我們需要求解次小生成樹的權值時,我們就要把在最小生成樹中沒有用過的邊,加上然後減去對應環中最大的路徑 return 0; }
Kruskal演算法實現:
kruskla演算法中我們列舉的邊權值會依次增大,那麼就會給我們計算提供一定的遍歷,但是因為kruskal的實現方式和prim有所不同,所以kruskal需要儲存當前最小生成樹中的節點,然後我們再去更新maxd陣列
#include <vector> #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define INF 0x3f3f3f3f using namespace std; int n,m; struct data { int u,v,w; bool vis; } p[20010]; vector<int>G[110]; int per[110],maxd[110][110]; bool cmp(data a,data b) { return a.w < b.w; } int Union_Find(int x) { return x == per[x] ? x: per[x] = Union_Find(per[x]); } void kruskal() { sort(p,p+m,cmp); for(int i=0; i<=n; i++)//初始化 { G[i].clear(); G[i].push_back(i); per[i]=i; } int sum=0,k=0;//sum是最小生成樹的值 for(int i=0; i<m; i++) { if(k==n-1) break; int x1=Union_Find(p[i].u),x2=Union_Find(p[i].v); if(x1!=x2) { k++; p[i].vis=1;//這條邊已經用過了 sum+=p[i].w; int len_x1=G[x1].size(); int len_x2=G[x2].size(); for(int j=0; j<len_x1; j++)//更新兩點之間距離的最大值 for(int k=0; k<len_x2; k++) maxd[G[x1][j]][G[x2][k]]=maxd[G[x2][k]][G[x1][j]]=p[i].w;//因為後面的邊會越來越大,所以這裡可以直接等於當前邊的長度 per[x1]=x2; int tem[110]; for(int j=0; j<len_x2; j++)//現在已經屬於一棵樹了,那麼我們就將點新增到相應的集合中 tem[j]=G[x2][j]; for(int j=0; j<len_x1; j++) G[x2].push_back(G[x1][j]); for(int j=0; j<len_x2; j++) G[x1].push_back(tem[j]); } } int cisum=INF;//次小生成樹的權值 for(int i=0; i<m; i++) if(!p[i].vis) cisum=min(cisum,sum+p[i].w-maxd[p[i].u][p[i].v]); if(cisum>sum) printf("%d\n",sum); else printf("Not Unique!\n"); } int main() { int T; scanf("%d\n",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0; i<m; i++) { scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w); p[i].vis = false; } kruskal(); } return 0; }