次小生成樹應用場景在於: 讓你求除了最小生成樹之外的那個 第二小的生成樹。
- Prim算法,次小生成樹:
原理是: 首先看看 純prim最小生成樹的模板, 我們不用改太多,只用額外維護一個數組,這個數組是 Max[a][b] ,意思是 任意兩個點a和b,樹上從a到b的路徑中的最大邊。
給人的感覺是挺難弄的,a到b路徑的最大邊,感覺是挺複雜的, 但是如果你熟悉 prim 的模板,請你看看下面的代碼,你就立刻明白了:
static int prim() {
for(int j=1;j<=105;j++)
for(int i=1;i<=105;i++) {
intree[i][j]=0; //不難理解,i-j 這條是不是在最小樹上,一開始都是0
Max[i][j]=0; // 傳說中的 i-j 的路徑中的最大邊
from[i]=0;
use[i]=0;
}
for(int i=1;i<=n;i++) { // 不多說, 維護一個near數組和前驅數組
near[i]=map[1][i];
from[i]=1;
}
use[1]=1;
int res=0;
for(int i=0;i<n-1;i++) { //n個點,找n-1個邊
int min=0x3f3f3f;
int p=0;
for(int j=1;j<=n;j++) { //循環找最小那個邊
if(use[j]==0 && min>near[j]) {
p=j;
min=near[j];
}
}
use[p]=1;
res+=min;
intree[p][from[p]]=intree[from[p]][p]=1;
for(int j=1;j<=n;j++) {
if(use[j]==1 && j!=p) { //唯一的區別, 維護一個Max數組,
Max[p][j]=Max[j][p]=Math.max(Max[from[p]][j],near[p]);
}
if(use[j]==0 && near[j]> map[p][j]) {
near[j]=map[p][j];
from[j]=p;
}
}
}
return res;
}
思考一下爲什麼max數組可以那樣的到,因爲 a-b路徑的最大邊,只可能是從 p點的前驅到j 和near[p]之間的最大的那個。 (樹上的從a到b的最大邊)
第二步就是 , 我們 遍歷不在 樹上的的邊, 比如 c -d ,它們不在樹上,但是我們把他倆連接起來,那麼就讓樹上有了個環, 這個時候再減去 Max[c][d] (在最小樹上的 從c到d的 最大邊), 這個時候就得到了對於 樹外邊 c-d 的 次小。
遍歷所有非樹上邊 , 遍歷找找哪個非樹上邊的 次小樹值最小 ,就是我們所要求的 次小樹。
例題:
uva10006 ,裸次小生成樹
//package 生成樹;
import java.io.BufferedInputStream;
import java.util.Scanner;
public class Main{
static int[][]intree, Max, map;
static int[] use,near,from;
static int n,m,ans;
static int prim() {
for(int j=1;j<=105;j++)
for(int i=1;i<=105;i++) {
intree[i][j]=0;
Max[i][j]=0;
from[i]=0;
use[i]=0;
}
for(int i=1;i<=n;i++) {
near[i]=map[1][i];
from[i]=1;
}
use[1]=1;
int res=0;
for(int i=0;i<n-1;i++) {
int min=0x3f3f3f;
int p=0;
for(int j=1;j<=n;j++) {
if(use[j]==0 && min>near[j]) {
p=j;
min=near[j];
}
}
use[p]=1;
res+=min;
intree[p][from[p]]=intree[from[p]][p]=1;
for(int j=1;j<=n;j++) {
if(use[j]==1 && j!=p) {
Max[p][j]=Max[j][p]=Math.max(Max[from[p]][j],near[p]);
}
if(use[j]==0 && near[j]> map[p][j]) {
near[j]=map[p][j];
from[j]=p;
}
}
}
return res;
}
static int prim2() {
int res=0x3f3f3f;
for(int i=1;i<=n;i++) {
for(int j=i+1;j<=n;j++) {
if(intree[i][j]==0) {
res=Math.min(res, ans-Max[i][j]+map[i][j]);
}
}
}
return res;
}
public static void main(String[] args) {
Scanner sc= new Scanner (new BufferedInputStream(System.in));
int test=sc.nextInt();
map=new int [200][200];
Max=new int [200][200];
intree=new int [200][200];
use=new int [200];
near=new int[200];
from=new int[200];
while(test-->0) {
n=sc.nextInt();
m=sc.nextInt();
for(int i=1;i<=n+2;i++) {
for(int j=1;j<=n+2;j++) {
map[i][j]=0x3f3f3f;
}
}
for(int i=0;i<m;i++) {
int a =sc.nextInt();
int b=sc.nextInt();
int c=sc.nextInt();
map[a][b]=Math.min(map[a][b], c);
map[b][a]=map[a][b];
}
ans=prim();
int res2=prim2();
System.out.println(ans+" "+res2);
}
}
}
kruskal算法:
比較直接 , 只用 記錄在樹上的所有邊。 簡簡單單先走一遍 純kruskal算法,然後,依次遍歷所有樹上的邊(假如MST上有m個邊), 再重新走一遍刪去這個邊的 kruskal算法(就是特判一下不要用到這個邊罷了),然後循環m次 ,找最小值就ok了
例題:
uva, 裸次小生成樹,因爲存在重複邊,使用 kruskal算法比較合適。
//package 生成樹;
import java.io.BufferedInputStream;
import java.util.Arrays;
import java.util.Scanner;
public class Main{
static int[] root, value,tree;
static int n,m,con1;
static class e implements Comparable<e>{
int u,v,w;
public e(int u,int v,int w) {
this.u=u;
this.v=v;
this.w=w;
}
@Override
public int compareTo(e o) {
return this.w - o.w;
}
}static e[]es;
static int find(int a) {
int temp;
if(a!=root[a]) {
temp = root[a];
root[a]= find(root[a]);
value[a]+= value[temp];
}
return root[a];
}
static void union(int a,int b) {
int roota=find(a);int rootb=find(b);
if(roota==rootb)return;
if(value[roota]>=value[rootb]) {
root[rootb]=roota;
if(value[roota]>value[rootb]) value[roota]++;
}else {
root[roota]=rootb;
}
}
static int kruskal() {//
for(int i=1;i<=n;i++) {
root[i]=i; //初始化並查集
value[i]=0;
}
Arrays.fill(tree, 0);
Arrays.sort(es, 1, m+1);
int res=0;
for(int i=1;i<=m;i++){
int rootu= find(es[i].u);int rootv=find(es[i].v);
if(rootu!=rootv) {
tree[++con1]=i;
res+=es[i].w;
union(es[i].u,es[i].v);
}
}
if(con1==n-1) return res;
else return -1;
}
static int kruskal_2( int a) { //去除第a條邊,找找現在的最小樹
for(int i=1;i<=n;i++) {
root[i]=i; //初始化並查集
value[i]=1;
}
int res=0;
int con=0;
for(int i=1;i<=m;i++){
if(a==i)continue;
int rootu= find(es[i].u);int rootv=find(es[i].v);
if(rootu!=rootv) {
con++;
res+=es[i].w;
union(es[i].u,es[i].v);
}
}
if(con==n-1)return res;
return 0x3f3f3f;
}
public static void main(String[] args) {
Scanner sc= new Scanner(new BufferedInputStream(System.in));
root=new int[105];
value=new int[105];
tree =new int[1000];
es=new e[1000];
int test=sc.nextInt();
for(int tt=1;tt<=test;tt++) {
n=sc.nextInt();
m=sc.nextInt();
for(int i=1;i<=m;i++) {
if(es[i]==null)es[i]=new e(0,0,0);
es[i].u=sc.nextInt();
es[i].v=sc.nextInt();
es[i].w=sc.nextInt();
}
con1=0;
int ttt=kruskal();
//System.out.println(ttt);
if(ttt==-1) {
System.out.println("Case #"+tt+" : No way");
continue;
}
int res=0x3f3f3f;
for(int i=1;i<=con1;i++) {
int t= kruskal_2(tree[i]);
res=Math.min(res, t);
}
if(res==0x3f3f3f) System.out.println("Case #"+tt+" : No second way");
else System.out.println("Case #"+tt+" : "+res);
}
}
}