[图论/迭代优化/动态规划]最大流问题 Edmonds-Karp算法(附POJ 1273解题)

图论中的最大流问题解法一般分为两类:

(1)增广路径方法。这个方法是由Ford-Fulkerson俩人提出来的,所以这一类的方法统称Ford-Fulkerson算法。增广路径又叫流量增益路径,增广的意思我个人理解是“可扩张的”,是由多条边。

这种方法总体思想是先找到一条从源点到汇点的增广路径,这条路径不管由多少条边组成,这条路径的容量只能是其中容量最小的边的容量。这其实就是桶的短板效应(我的理解是在图论中也就是最大流最小割定理)。如果我们能找到所有这样的路径(路径是可部分重叠的),那么最后一定能得到最大流。(证明就是最大流最小割定理)。当然,我们每找到一条之后,图其实是发生了一些变化的,我们必须做出一定的标记。所以这个算法不仅有迭代优化的思想(最大流算法实际上是线性规划的一种),也有标记的思想。

此类算法的不同之处在于找到所有增广路径的方法不同:Edmonds-Karp算法(下面简称EK算法)使用BFS(广度优先搜索);Dinic算法使用DFS和层次图。

(2)预流法。我还没有看。。。主要包括:Push-relabel/Relabel-to-front/SAP算法等。

关于网络流问题,很多算法书上采用的讲解方式不尽相同。例如《数据结构与算法分析C++描述》(第三版中文版)中就采用的是残余图的方法,只讲了基本的Ford-Fulkerson方法,既无伪代码,又无具体的路径寻找方法;《算法设计与分析基础》(第二版中文版)采用了正向边和反向边结合的讲解,如果能同时对照残余图的思想则更好,而且此书还给出了EK算法的伪代码和算法思想。因此个人推荐后者作为参考书,本文中的代码也是基于EK算法的。

(一)如何确定一条路径的流量:标号法

根据EK算法,路径上的每个点都需要两个标记:一个标记从源点到当前节点实际还剩多少流量可用;另一个标记在这条路径上当前节点的前驱。

下面解释一下这两个标记,设当前节点下标为j,其第一个标记我们设为flow[j]。其前驱节点下标为i,其标记为flow[i]。另外设i与j之间的边的容量为x。

它们二者的关系遵循以下的公式:

flow[j] = min( flow[i], x)

这是为什么呢?我们定性的讨论一下。节点j的流量肯定通过边ij,来自于其前驱的节点i的。如果边的容量x大于节点i的流量,那么边ij并不能被充满;如果边的容量x小于节点i的流量,那么这些流量也不能全部经过边ij进入节点j。所以节点j的流量只能取其二者中更小的。

那么我们经过从源点到汇点的这种迭代(有点动态规划的意思:一个状态只跟其前一个状态有关系),最终就得到了这条路径上最小的流量(被短板限制的流量)。

(二)为什么要有反向边或者残余图?

前面我们确定了一条路径的最小流量之后,我们实际上可以把这部分流量从图中删去(这个可以看做减治的过程):因为这个路径以后是不会更改的,而且路径之间互不影响。但是也会出现一定的问题:结果往往不是最优的。反向边和残余图可以解决这一问题(但是其证明极其复杂)。

(三)找到所有的路径

我们知道一次BFS或者DFS就能保证我们能找到图上所有的从源点到汇点的路径。这个就不再多说了。

(四)EK算法代码(实际上是POJ 1273题的解答)

题目见:http://acm.pku.edu.cn/JudgeOnline/problem?id=1273

本代码仅针对POJ的题目写的,如果挪作它用,还得改写一些地方。

   1 /*
   2 author : MicroGrape
   3
question : POJ 1273 Drainage Ditches
   4
category : network max flow
   5 */

   6
   7 #include <iostream>
   8 #include <queue>
   9
10 using namespace std;
11

12 const int NODES = 210 ;
13 const int INF = 0x7FFFFFFF
;
14

15 //the flow network
16 int capacity[NODES][NODES];
17 //record flows from one vertex to next

18 int flow[NODES];
19 //
record the previous node in the temporary path
20 //if prev[i] != -1,means it's visited.

21 int prev[NODES];
22 //number of nodes in fact

23 int num;
24

25 //use Breadth First Search to find an augment path
26 int BFS(int src, int dest)
27
{
28      queue<int>
next;
29     while(!
next.empty())
30
         next.pop();
31

32     for( int i = 0; i < NODES; ++ i )
33          prev[i] = -1
;
34

35      next.push(src);
36      prev[src] = 0
;
37      flow[src] =
INF;
38

39     while( ! next.empty() )
40
     {
41         int index =
next.front();
42
         next.pop();
43

44         //reach the destination
45         if( index == dest )
46             break
;
47         //find unvisited and adjacent vertex that are not full

48         for(  int j = 1 ; j <= num ; ++ j )
49
         {
50             if( j != src && capacity[index][j] > 0 && prev[j] == -1
)
51
             {
52                 //key step : find the minimun flow increment iteratively

53                  flow[j] = min(capacity[index][j], flow[index]);
54
                 next.push(j);
55                  prev[j] = index;//record the path

56              }
57
         }
58
     }
59     //not reach destination point

60     if(prev[dest] == -1 )
61         return -1
;
62     //return the minimum flow increment

63     return flow[num];
64
}
65

66 //key function
67 int maxflow(int src, int dest)
68
{
69     int max_flow = 0
;
70

71     int increment = 0 ;
72     while( ( increment = BFS(src, dest) ) != -1
)
73
     {
74          max_flow +=
increment;
75

76         int k = dest;
77         while( k !=
src)
78
         {
79             int last =
prev[k];
80             //key step: update the flow, iteratively

81              capacity[last][k] -= increment;//decrease the forward edge
82              capacity[k][last] += increment;//add an opposite edge
83              k = last;
84
         }
85
     }
86     return
max_flow;
87
}
88

89 int main()
90
{
91     //input the first line

92     int edges ;
93     while( cin>>edges>>
num )
94
     {
95         //input each edge

96         int u, v, value;
97          memset( capacity, 0, sizeof
(capacity) );
98         for(int i = 0 ; i < edges ; ++
i )
99
         {
100              cin>>u>>v>>
value;
101             if( u ==
v )
102                 continue
;
103              capacity[u][v] +=
value;
104
         }
105          cout<<maxflow(1, num)<<
endl;
106
     }
107

108     return 0 ;
109
}
110

附:参考资料

网上的资料比较芜杂,代码往往没有注释。

http://fuliang.javaeye.com/blog/372845 给出了一个比较清晰的java实现的版本,虽然有点小错误,不可以直接使用。






你可能感兴趣的:(动态规划)