最短增廣路演算法(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 00 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;
}
}