自己学习《图论》的基础算法,终于走到最大流的地盘了 呵呵……我可是足足整了三天啊!!!
开始看ppt,讲述Ford_Fulkerson,尽管这个算法已经被更加优秀的 Edmonds-Karp算法所取代。我研究这个算法的目的其实在于 Edmonds-Karp算法,没办法,谁叫 Edmonds-Karp算法是由Ford_Fulkerson改进过来的呢!
把ppt看懂过后就自己上代码了。结果真的是让人崩溃:有向图,流竟然可以回退!意味着有向图必须用无向图的思路来考虑!
我自己写代码,优先考虑的是DFS。因为我觉得既然是从起点到汇点。DFS比较快嘛!我傻糊乎乎的照搬ppt上面的实现过程,把边提取出来,用一个类记录边的信息,然后用DFS去搜索,边搜边改。导致的结果是我连续改版了8次,一步步跟踪调试,当然注定是失败的!尽管最后都没成功,却让我一点一点清晰地理解了这个算法的实现过程! 最后决定研究别人的代码了,不管三七二十一,先把代码贴出来吧! 希望不会有盗用代码之嫌!
- package com.xh.Ford_Fulkerson;
- import java.util.LinkedList;
- import java.util.Queue;
- import java.util.Scanner;
- /*
- * 6 10 // 6 nodes, 10 edges
- 0 1 16 // capacity from 0 to 1 is 16
- 0 2 13 // capacity from 0 to 2 is 13
- 1 2 10 // capacity from 1 to 2 is 10
- 2 1 4 // capacity from 2 to 1 is 4
- 3 2 9 // capacity from 3 to 2 is 9
- 1 3 12 // capacity from 1 to 3 is 12
- 2 4 14 // capacity from 2 to 4 is 14
- 4 3 7 // capacity from 4 to 3 is 7
- 3 5 20 // capacity from 3 to 5 is 20
- 4 5 4 // capacity from 4 to 5 is 4
- */
- public class Ford_Fulkerson09 {
- private int capacity[][];
- private int flow[][];
- private boolean visited[];
- private int pre[];//通过pre记录了路径
- private int nodes;
- static int count=0;
- static int[][] map = { { 0, 2, 9, 3, 0 },// 三条边
- { 0, 0, 7, 0, 8 },// 两条边
- { 0, 6, 0, 4, 0 },// 两条边
- { 0, 0, 0, 0, 5 },// 一条边
- { 0, 0, 0, 0, 0 } //
- };
- public Ford_Fulkerson09(int[][] capacity, int nodes) {
- this.capacity = capacity;
- this.nodes = nodes;
- this.flow = new int[nodes][nodes];
- this.pre = new int[nodes];
- this.visited = new boolean[nodes];
- }
- public int maxFlow(int src, int des) {
- int maxFlow = 0;
- for (int i = 0; i < nodes; i++)
- for (int j = 0; j < nodes; j++)
- flow[i][j] = 0;
- while (true)// find a augment path
- {
- for (int i = 0; i < nodes; i++) {
- visited[i] = false;
- }
- pre[src] = -1;
- if (!BFS(src, des)) {// the BFS
- System.out.println("break::::"+count);
- break;
- }
- /*
- * DFS(src,des);//DFS if(!visited[des]) break;
- */
- int increment = Integer.MAX_VALUE;
- System.out.println("path:" +(count++));
- for (int i = des; pre[i] >= src; i = pre[i]) {
- // find the min flow of the path
- increment = Math.min(increment, capacity[pre[i]][i]
- - flow[pre[i]][i]);
- System.out.print(i+",");
- }
- System.out.print(0+",");
- System.out.println();
- // update the flow
- //对记录的路径的处理从终点开始,到起点 这里的处理是很巧的
- for (int i = des; pre[i] >= src; i = pre[i]) {
- flow[pre[i]][i] += increment;
- flow[i][pre[i]] -= increment;//由于需要考虑到退回边,所以这里需要减少
- /*不能不惊叹于这种处理 本来我们的流是没有考虑负值的
- * 而且的话,向前流的回退流需要两种不同的处理,而这里作者直接让回退流为负
- * 这样的话,在bfs访问的过程中就不需要考虑两个点边的方向,这样就可以当作无
- * 向图的来处理了*/
- }
- // increase the maxFow with the increment
- System.out.println("==============flow==================");
- for (int i = 0; i < flow.length; i++) {
- for (int j = 0; j < flow.length; j++) {
- System.out.print(flow[i][j]+" ");
- }
- System.out.println();
- }
- System.out.println("increment======="+increment+"============");
- maxFlow += increment;
- }
- return maxFlow;
- }
- private boolean BFS(int src, int des) {
- Queue<Integer> queue = new LinkedList<Integer>();
- queue.add(src);
- visited[src] = true;
- System.out.print(queue.peek()+",");
- while (!queue.isEmpty()) {
- int node = queue.poll();
- for (int i = 0; i < nodes; i++) {
- if (!visited[i] && (capacity[node][i] - flow[node][i] > 0)) {// 只要满足条件的点都加进去,而不会考虑两个点的边是正向边还是回退边
- /*
- * capacity[node][i] - flow[node][i] > 0
- * 如果刚开始,回退边的flow是0,capacity也是0,不会被选择
- * 但是回退边的flow会被设为负值,所以后来遇到回退边的时候, 回退边就会被考虑进来了 尽管capacity为0
- */
- queue.add(i);
- visited[i] = true;
- pre[i] = node;// record the augment path
- System.out.print(i+",");
- }
- }
- }
- System.out.println("======================pre"+count+"================");
- for (int i = 0; i < pre.length; i++) {
- System.out.print(pre[i]+",");
- }
- System.out.println();
- return visited[des];
- }
- public static void main(String[] args) {
- int nodes;
- nodes = map[0].length;
- int[][] capacity = new int[nodes][nodes];
- for (int i = 0; i < nodes; i++) {
- for (int j = 0; j < nodes; j++) {
- if (map[i][j] != 0) {
- capacity[i][j] = map[i][j];
- }
- }
- }
- Ford_Fulkerson09 maxFlow = new Ford_Fulkerson09(capacity, nodes);
- System.out.println(maxFlow.maxFlow(0, nodes - 1));
- }
- }
由于代码中加入了打印输出,所以显得有点乱。 如果研究,最好先把里面大量的输出去掉。原图如下
当然这个图用矩阵的方式来存储,其实由于开始看图论是看matlab的原因,习惯了矩阵。
这个程序的实现有以下几点我觉得是很值得赞赏的:
采用BFS搜索,只记录可以实现的路径。而且在对路径的处理
// update the flow
//对记录的路径的处理从终点开始,到起点 这里的处理是很巧的
for (int i = des; pre[i] >= src; i = pre[i]) {
flow[pre[i]][i] += increment;
flow[i][pre[i]] -= increment;//由于需要考虑到退回边,所以这里需要减少
/*不能不惊叹于这种处理 本来我们的流是没有考虑负值的
* 而且的话,向前流的回退流需要两种不同的处理,而这里作者直接让回退流为负
* 这样的话,在bfs访问的过程中就不需要考虑两个点边的方向,这样就可以当作无
* 向图的来处理了*/
}
然后就是为流量的变化建立了一个跟capacity一样大小的矩阵。当然空间开销有点大。
再就是bfs实现的细节。当时我自己的考虑纠结在有向图边方向的问题上了。他这里直接绕过去了……给人的感觉好像根本就不用考虑边的方向,甚至都不用考虑边!只要满足:流量<容量 这个条件就够了!而其实,这个条件包含了以下的隐藏的条件:比如:两点一定会有边的,因为有边才有流量。如果是回退流,那当然其容量为0,但只要流量为负就ok了!
当然,我觉得在BFS搜索的过程中,其实做了许多无用的搜索。 差不多是每个点必须搜索到,如果汇点搜索不到的话程序就可以结束了 呵呵!