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

}






猜你喜欢

转载自blog.csdn.net/qq_39020387/article/details/80021851