1. 程式人生 > >最短增廣路演算法(SAP)基本模板JAVA

最短增廣路演算法(SAP)基本模板JAVA

SAP基本思路:

準備好兩個陣列 vis[i]和pre[i],    1)vis[i]用來標記節點i是否被訪問過,2)pre[i]用來記錄節點i的前驅節點,(用來記錄發現的增廣路)

準備好兩個陣列g[i][j]和map[i][j],   1)g[i][j]代表殘餘網路,殘餘網路中將由原點方向指向匯點方向的邊稱為“可增量邊”,每條可增量邊都有一條與之對應但方向相反的“實流邊”,sap尋找可增廣路主要依據的就是殘餘網路。   2)但是在對g[][]陣列進行操作過後,就無法分辨哪些是“實流邊”和“可增量邊”,必須依據一個map陣列(實流網路)來記錄哪些是真正的實流邊。

然後 1>重置pre陣列和vis陣列, 對殘餘網路g[][] 進行bfs搜尋,找到一條從start——end的可增廣路,用pre陣列以倒序將之記             錄下來。

       2>根據記錄好的一條存貯在pre陣列中的可增廣路,迴圈比較計算該條路徑上的最大流max,更新  maxflow+=max.

           迴圈更新(從後向前,方便說明:end代表靠近匯點一側,start代表靠近原點一側,也就是說,殘餘網路中可增量邊是              start——》end,實流邊則反之)  殘餘網路 g 和  實流網路map,   在殘餘網路中,再計算過一次路徑的最大流之後,                  g[start][end](可增量邊)權值要減少max,反之g[end][start](實流邊)權值要增加max。

           而在實流網路map中 如果map[end][start]>0,即該邊的方向是反向的,那麼一定要減流,如果map[end][start]<=0,則說            明是正向邊一定要增流。

一個測試用例:

6
9
1 2 12
1 3 10
2 4 8
3 2 2
3 5 13
4 3 5
4 6 18
5 4 6
5 6 4

輸出:

18

(v1~v6節點)

0 8 10 0 0 0 
0 0 0 8 0 0 
0 0 0 0 10 0 
0 0 0 0 0 14 
0 0 0 6 0 4 

0 0 0 0 0 0 

程式碼:

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class 最大網路流_最短增廣路演算法 {

	/**
	 * @param args
	 */
	static final int INF=Integer.MAX_VALUE;
	public static void main(String[] args) {
		 Scanner sc=new Scanner(System.in);
		 int n=sc.nextInt();
		 int m=sc.nextInt();
		 int g[][]=new int[n+1][n+1];//殘餘網路
		 int map[][]=new int[n+1][n+1];//實流網路
		 for (int i = 1; i <= m; i++) {
			int a=sc.nextInt();
			int b=sc.nextInt();
			int w=sc.nextInt();
			g[a][b]+=w;//殘餘網路的可增量,初始化為改邊的容量
		}
		int vis[]=new int[n+1];
		int pre[]=new int[n+1];
		int res=sap(map,g,vis,pre,1,n);
		System.out.println("最大流量為"+res);
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				System.out.print(map[i][j]+" ");
			}
			System.out.println();
		}
	}
	/**
	 * 根據bfs搜尋確定的pre前驅表裡儲存的增廣路徑,
	 * 更新最大流maxflow,更新殘餘網路,和實流網路
	 * @param map 實流網路
	 * @param g 殘餘網路
	 * @param vis
	 * @param pre
	 * @param start 初始節點(原點)
	 * @param end 結束節點(匯點)
	 * @return
	 */
	private static int sap(int[][] map, int[][] g, int[] vis, int[] pre, int start,
			int end) {
		int maxflow=0;//最大流
		while (bfs(start,end,vis,pre,g)) {
			int min=INF;//最小增量
			//在找到的一條(從後向前)可增廣路徑上,找到最大增量min(可增量中的最小值)
			int l=end;//臨時量,驅動向前找pre中的路徑
			int temp;
			while (l!=start) {
				temp=pre[l];//l的前一個節點
				if(min>g[temp][l]){
					min=g[temp][l];
				}
				l=temp;//更新臨時量,繼續沿增廣路徑向前搜尋
			}
			
			maxflow+=min;//更新最大網路流
			l=end;
			while (l!=start) {
				temp=pre[l];
				g[temp][l]-=min;//殘餘網路 “可增量邊”減流,正向(從原點方向指向匯點方向)
				g[l][temp]+=min;//殘餘網路“實流變”增流,反向(從匯點方向指向原點方向)
				//實流網路中,如果是反向邊則減流,否則正向邊增流
				//因為實流網路的初始值都是0,所以一開始都是增加流量
				if(map[l][temp]>0){//反向邊,大於0存在實流
					map[l][temp]-=min;//減去最大增量,減流
				}else {
					map[temp][l]+=min;//增流
				}
				l=temp;
			}
			
		}
		return maxflow;
	}
	/**
	 * 這個bfs的目的是找到一條可增廣路,並存儲在前驅表pre中,返回true表示存在這樣一條路徑,
	 * false表示不存在
	 * @param start 原點
	 * @param end 匯點
	 * @param vis 標記表
	 * @param pre 前驅表
	 * @param g 殘餘網路
	 * @return
	 */
	private static boolean bfs(int start, int end, int[] vis, int[] pre, int[][] g) {
		//重置前驅節點表pre,和標記表vis,因為每次都是在重新找一條可增廣路,所以必需重置pre和vis表
		for (int i = 0; i < pre.length; i++) {
			pre[i]=-1;
			vis[i]=0;
		}
		int n=vis.length-1;//節點個數
		Queue<Integer> q=new LinkedList<Integer>();
		vis[start]=1;//標記初始節點已經走過
		q.offer(start);//初始節點入隊
		while (q.size()!=0) {
			int temp=q.poll();//佇列首節點出隊
			//遍歷所有節點,尋找滿足條件的鄰接節點
			for (int i = 1; i <= n; i++) {
				//沒有被訪問過,而且是temp的鄰接節點
				if(vis[i]==0 && g[temp][i]>0){
					vis[i]=1;//標記訪問過
					pre[i]=temp;//記錄該節點的前驅為temp
					if(i==end){//如果新節點i,到達匯點end,那麼結束遍歷
						return true;
					}
					q.offer(i);//否則新節點入隊
				}
			}
		}
		return false;
	}

}