最大流可以简化成以下模型,顶点表示流需要通过的节点,边的容量和边的方向对经过该条边的流的大小加以限制,现在求从开始节点到终点的最大流量是多少
最大流问题解法设计一个定理:对于这张图随便且切一刀,把图分成分别包含源节点和终点节点的2部分,最大流的流量不可能超过经过该切线的边的容积的有向和(从源到终点为正向记为正数,从终点到源为逆向,记为负数)
这点很容易理解 所有的流都是从源到终点,这些流必须经过这个切面,这个切面允许的最大流量就是计算出来的有向和
从上面的定理我们就可以得出一个思路:如果把最大流占用的空间从网路图中去除,那么最后剩下的图中不存在从源到终点的一条简单路径。
所以我们需要做的就是在图中不断找源到终点的路径,并在图中去除,直到找不到从源到目标的路径
在找路径的时候我们面对的图有可能不是原始图,而是增广图。增广图的定义是存在着源到终点路径的图,增广图不仅包含着边的剩余容量,还包含了后向边。如下图例子
沿着1-2这两边 我们可以找到 1- 2 -3 -5 容量为2的路径
然后我们找到1-2-4-5 容量为2的路径
如果不考虑增广图我们只能找到1-3-5 容量为1的路径
最后结果为5 这个显然不对
这是因为我们之前确定的路径是一条从源到终点的路径,但是由于路径确定的前后关系,部分路径的流量大小可能不符合最大流量图的要求,这些会影响在其他路径中流量承载的能力。
解决这个问题的方法是确定完路径之后把这条路径的反向路径考虑进去,这也就达到了流量回退的效果。
1流量回退是用新产生的流量代替之前传到该节点流量在之前的路径中继续执行 这点保存流量回退不会大致总流量减少
2退回去的流量将会沿着某条路径流回终点,这点保证了流量回退能保证总流量增加
流量回退保证了每条路径都被充分利用
在上图的例子中 我们可以找到 1- 2 -3 -5 容量为2的路径
然后我们找到1-2-4-5 容量为2的路径
我们找到1-3-5 容量为1的路径
我们找到1-3-2-4 容量为2的路径
下面是java版的代码
import java.util.ArrayList; import java.util.Scanner; //这里定义最大流的模型 //把网状图中的所有节点编号1-N 默认是找到1到N的最大流 //中间过程可能产生反向边,这部分在初始的时候容量标为0表示不连通 //寻找最大流的过程用DFS public class MaxFlow { public static void main(String[] args) { System.out.println(MaxFlow.getMaxFlow()); } static final int maxSize=201; static ArrayList[] VerticLink=new ArrayList[maxSize];//用来存顶点的连接表 static int[][] capability = new int[maxSize][maxSize];//存当前容量 static int mapSize =0;//存图中的顶点的个数 static Boolean[] Accessed = new Boolean[maxSize]; //找出一条从源到终点的路径 找不到则返回0 private static int dfs(int source,int terminal,int FlowLimit){ if(source == terminal){ return FlowLimit; } Accessed[source] = true; for(int i = 0 ; i < VerticLink[source].size();i++){ int next = (Integer)VerticLink[source].get(i); if((!Accessed[next])&&capability[source][next]>0){ int newFlowLimit = FlowLimit>capability[source][next] ? capability[source][next]:FlowLimit; int reval = dfs(next, terminal, newFlowLimit); if(reval > 0){ capability[source][next] -=reval; capability[next][source] -=reval; return reval; } } } Accessed[source] = false; return 0; } //输入图 private static void getTheMap(){ Scanner in = new Scanner(System.in); mapSize = in.nextInt(); for(int i = 1 ; i <= mapSize;i++){ VerticLink[i] = new ArrayList<Integer>(); } int eages = in.nextInt(); int x = 0 ; int y = 0; for(int i = 1 ; i <=eages ; i++ ){ x = in.nextInt(); y = in.nextInt(); capability[x][y] = in.nextInt(); VerticLink[x].add(y); VerticLink[y].add(x); } } //处理最大流的问题 public static int getMaxFlow(){ getTheMap(); int result =0; while(true){ for(int i=1; i <=mapSize;i++){ Accessed[i] = false; } int f = dfs(1, mapSize, Integer.MAX_VALUE); if(f <= 0 ){ break; }else{ result +=f; //保存已经找出的输出流总量 } } return result; } }
在写这个程序的时候我有对边的表示进行过考虑
方案一
直接用一个N*N的邻接矩阵
好处 简单 理解容易
缺点 对于连线不多的图 空穴太多 对于顶点的邻接点的寻找很费时
方案二
用一个可变长数组存
E.G. ArrayList[] 的形式 这样能省下遍历邻接点的时间
缺点 可变长数组增长耗时(如果顶点数确定 可以直接开一个顶点数大小的arrayList 然后trimtoSize) 由于如果确定 x y的 那么需要在y的邻接点中找到x才能对于y到x边的容量操作 也费时
方案三
折中
用ArrayList[] 存顶点的连接情况 然后把容量填到邻接矩阵中
优点: 时间
缺点:空间消耗大
这是一个典型要用空间换时间的方案