次小生成树应用场景在于: 让你求除了最小生成树之外的那个 第二小的生成树。
- 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);
}
}
}