次小生成樹-JAVA-兩個模板

次小生成樹應用場景在於: 讓你求除了最小生成樹之外的那個 第二小的生成樹。

  1. 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);
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章