图论 —— 生成树 —— 次小生成树
【概述】
對于給定的無向圖 G=(V,E),設 T 是圖 G 的一個最小生成樹,那么,對于除 T 外的第二小的生成樹 T' 即為圖的次小生成樹。
簡單來說,最小生成樹是生成樹的最小解,次小生成樹是生成樹的次小解,它有可能和最小生成樹的值一樣,但肯定不能比最小生成樹的值要小。
一般來說,求最小生成樹的算法是 Prim 或 Kurskal,那么對于次小生成樹,同樣可以使用這兩種算法來解。
對于求次小生成樹來說,兩種算法的思路都是相同的。首先求出最小生成樹,再枚舉每條不在最小生成樹上的邊,并把這條邊放到最小生成樹上面,此時一定會形成環,那么在這條環路中取出一條除新加入的邊外的最長路,最終得到的權值就是次小生成樹的權值。
【Prim 求解次小生成樹】
使用 Prim? 求解次小生成樹需要使用一個二維數組 maxDis[i][j] 來表示最小生成樹中 i 到 j 的最遠距離,其是使用動態規劃的思想來計算的,例如:當前節點為 x,其父節點為 per[x],根節點為 root,那么 maxDis[root][x] = max(maxDis[root][per[x]] , maxDis[per[x]][x]);
此外,還需要一個二維數組 connect[i][j] 表示最小生成樹中這條邊有沒有被用到,剩下的就是模擬算法中所說的刪邊以及添邊的操作。
int n,m,G[N][N]; bool vis[N],connect[N][N]; int dis[N],maxDis[N][N],per[N]; int prim(){memset(maxDis,0,sizeof(maxDis));memset(vis,false,sizeof(vis));for(int i=1;i <=n; i++){dis[i]=G[1][i];per[i]=1;//父節點都是根節點}vis[1]=true;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;maxDis[per[index]][index]=temp;//更新點之間的最大值maxDis[index][per[index]]=temp;//更新點之間的最大值for(int j=1; j<=n; j++){if(j!=index && vis[j]){//更新已遍歷過的節點maxDis[index][j]=max(maxDis[j][per[index]],dis[index]);maxDis[j][index]=max(maxDis[j][per[index]],dis[index]);}if(!vis[j] && G[index][j]<dis[j]){dis[j]=G[index][j];per[j]=index;}}}return res; } int main(){scanf("%d%d",&n,&m);memset(G,INF,sizeof(G));memset(connect,false,sizeof(connect));for(int i=0; i<m; i++){int u,v,w;scanf("%d%d%d",&u,&v,&w);G[u][v]=w;G[v][u]=w;connect[u][v]=true;connect[v][u]=true;}int res=prim();//求次小生成樹bool flag=false;for(int i=1; !flag && i<=n; i++){//枚舉點for(int j=1 ; j<=n; j++){//枚舉邊//某邊未被使用或i~j的最大值大于圖中最大值,次小生成樹存在if(connect[i][j]==false || G[i][j]==INF)continue;//邊長度相同時表示最小生成樹相同,次小生成樹不存在if(G[i][j]==maxDis[i][j]){flag=true;break;}}}if(flag)printf("Not Unique!\n");elseprintf("%d\n",res);return 0; }【Kurskal 求解次小生成樹】
Kruskla 算法中枚舉的邊權值會依次增大,那么就會給計算提供一定的便利,但因為 Kruskal 的實現方式和 Prim 有所不同,因此 Kruskal 需要存儲當前最小生成樹中的節點,然后再去更新 maxDis?數組
int n,m; struct Node{int u,v,w;bool vis;bool operator <(const Node &rhs)const{return w<rhs.w;} } node[N]; vector<int>G[1000]; int father[1000],maxDis[1000][1000]; int Find(int x){return x==father[x]?x:father[x]=Find(father[x]); } void Kruskal(){sort(node,node+m);for(int i=0; i<=n; i++){//初始化G[i].clear();G[i].push_back(i);father[i]=i;}int mst=0,k=0;//k為當前生成樹中的點for(int i=0; i<m; i++){//枚舉邊if(k==n-1)//等于n-1個點break;int x=Find(node[i].u),y=Find(node[i].v);if(x!=y){k++;mst+=node[i].w;node[i].vis=true;//邊已用過,標記int lenX=G[x].size();int lenY=G[y].size();for(int j=0; j<lenX; j++){//更新兩點之間距離的最大值for(int k=0; k<lenY; k++){maxDis[G[x][j]][G[y][k]]=node[i].w;//因為后面的邊會越來越大,所以這里可以直接等于當前邊的長度maxDis[G[y][k]][G[x][j]]=node[i].w;}}father[x]=y;for(int j=0; j<lenX; j++)G[y].push_back(G[x][j]);}}int cimst=INF;//次小生成樹權值for(int i=0; i<m; i++)if(!node[i].vis)cimst=min(cimst,mst+node[i].w-maxDis[node[i].u][node[i].v]);if(cimst>mst)printf("%d\n",mst);elseprintf("Not Unique!\n"); } int main(){scanf("%d%d",&n,&m);for(int i=0; i<m; i++){scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);node[i].vis=false;}Kruskal();return 0; }?
總結
以上是生活随笔為你收集整理的图论 —— 生成树 —— 次小生成树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 借教室(洛谷-P1083)
- 下一篇: 图论 —— 生成树 —— 曼哈顿距离最小