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;
}
}