Ford-Fulkerson算法

本文讲解Ford-Fulkerson算法的思路算法本身与证明最后会给出模板Ford-Fulkerson算法是增广路算法的一种用于求解最大流问题不明背景请看网络流这篇文章

分析

朴素算法

朴素的想法的一种是这样的在图中DFS一旦找到一条从s指向t并且可以在这条路径上添加流量每条边$e$都满足$f_e<w_e$那么就在这条路径的所有边上同时不断添加流量直到无法再添加某条边$e$$f_e=w_e$直到无法再找到任何这样的路径即得到最大流如下图

朴素想法求最大流

上面的图中黑色为边的容量红色为边的容量图1为原来的网络其中1为源点6为汇点图二为DFS时可能得到的一个图可以看到上面的算法在这里就会停止而图三是该网络的最大流可以轻松地验证这一点因为从1出发的边都已经满流了无法再增加新的流量注意DFS可能得到最优解但不是一定得到最优解

所以上述的算法是错误的但是它错在哪里呢

改进

观察图3这个流比图2多的流如图4

朴素想法与最大流之差

从图4看它是把图2中$2\rightarrow4$已有的流量1推了回去而产生的这也正是增广路算法的核心所在但是这看起来不可理喻原图中没有的边为什么凭空造出来一条就正确呢

解释

仔细观察图2和图3就会发现$4\rightarrow6$的边流量没变仍然是1但是$2\rightarrow4$的流量没有了那最大流中谁为4提供那一个流量呢显然是$1\rightarrow3\rightarrow4$的路径同理$1\rightarrow2$的流量也没有变它从$2\rightarrow5\rightarrow6$的路径流出去了这就可以想象成节点4不知道是谁给他的流量也就是在这个过程中4的上游的信息被屏蔽了同时2的下游的信息也被屏蔽了只要有人能为4提供流量他就什么都不知道同样的只要有人能从2运走流量他也什么都不知道这样在新的流中原本只有一份的流量$1\rightarrow3\rightarrow 4$流走了一份$1\rightarrow2\rightarrow5$又流走了一份所以流量+1

所以$\langle u,v\rangle$的反向边$\langle v,u\rangle$的意义就是$\langle u,v\rangle$有流$f$的时候如果有一个路径可以从$v$流入称其前面的那段路经为上游再从$u$流出称其后的那段路径为下游且流为$f_0\le f$那么就可以认为上游为$v$提供原来的一部分流$f_0$而从$u$流入的那些$f_0$改道从下游流出了

上面的做法可以算作对朴素DFS做法的一个修正大概可以理解为避免了因DFS顺序不确定而导致的错误

于是对一个流可以对图上所有有流$f$的边$\langle u,v\rangle$建立反向边$\langle v,u\rangle$其容量为$f$这样得到的新的图称为残余网络残余网络上从$s$$t$的路径叫做增广路残余网络上进行dfs沿着增广路来添加流这就是Ford-Fulkerson算法

算法描述

对网络上的每个流$G(V,E)$定义与之对应的残余网络$G'(V,E')$$f_e>0(e=\langle u,v\rangle\in E)$那么$e\in E'$$rev(e)=\langle v,u\rangle\in E'$并且$w_{rev(e)}=f_e,f_{rev(e)}=0$$E'$中的元素只有刚才描述的那些那么算法可以描述为

Ford-Fulkerson算法

输入网络$G$一个图$G(V,E)$$E$中的边$e$有权重$w_e\in\mathbb{N_+}$$max\_flow=0$

输出该网络上的最大流的流量$f$

步骤0. [建立反向边]定义$max\_flow\gets0$用邻接表G[V]存储网络$E$中的每条边$e=\langle u,v\rangle$作其反向边$e'=\langle v,u\rangle$并置$w_{e'}\gets0$

步骤1. [寻找增广路]在$G$上DFS寻找从$s$$t$的简单路径要求该路径上每条边可用权值$(w_e-f_e)$大于0若找到记该路径上的权值最小的边权值为$f$否则$f\gets0$

步骤2. [找到了更新残余网络]如果$f>0$$max\_flow\gets max\_flow+f$对路径上的每条边$e$$f_e\gets f_e+f$记其反向边为$e'$$w_{e'}\gets w_{e'}+f$返回1

步骤3. [没有找到]如果$f=0$输出$max\_flow$算法结束

最大流不会超过从$s$出发的所有边的边权和而该算法中每次进入步骤1后$f$总是正整数也有$f=0$的情况此时算法结束所以该算法对整数总是有限的若记最大流流量上界为$F$则该算法最多进入步骤1$(F+1)$而每次进入步骤1需要$O(|E|)$的复杂度进行搜索所以总的时间复杂度是$O(F|E|)$但这个复杂度上界是很松的实际用起来效率比较高所以有时即使估计复杂度偏高也可以使用$F$较小的情况则可以轻松应对

证明

在Ford-Fulkerson算法结束后的残余网络上没有$s\rightarrow t$路径所以残余网络上$s$可达的所有点构成的集合$S$不包含$t$$T=V-S$那么$T$不空并且$T\cap S=\varnothing$

结论1$S$指向$T$满足$u\in S,v\in T$的边$e=\langle u, v\rangle$都应当满流$w_e=f_e$否则由于从$s$可达$u$$s$也可以从$s\rightarrow u\rightarrow v$到达$v$那么$v\in S$矛盾

结论2$T$指向$S$满足$u\in T,v\in S$的边$e=\langle u,v\rangle$流量必为0否则$s$可由其反向边到达$u$流入$S$的流量为0反向边就是在这里用到的

对于任意一个流$S-\{s\}$中的点流入的流量都等于流出的流量那么流入$S-\{s\}$的流量$f_{in}$等于从$S$流向$T$的所有边的流量和$S_f$这个和不超过从$S$流向$T$的所有边的容量和$S_w$写在一起就是$f_{in}=S_f\le S_w$从而从$s$流出的流量$f_s$一定不超过$S_w$因为$f_s$应当是$f_{in}$的一部分$f_{s}\le S_w$这是对任意流的结论

而对于残余网络注意残余网络也是一个流由结论1$S_f=S_w$又由结论2$f_s$即为$f_{in}$的全部所以有$f_s=f_{in}=S_w$所以$f_s$为所有流中的最大者

也可以用反证法若存在比Ford-Fulkerson算法得到的流更大的流仍然观察上面的点集$S$此时从$s$流出的流量$f_{s'}$应当超过了$f_x=S_w$这时$S-\{s\}$中的点不可能满足流入每个顶点的流量等于流出的流量所以这样的流不存在

至此就证明了Ford-Fulkerson算法的正确性

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>

#define V 5005 //V为顶点最大个数视数据范围而定
#define INF 0x7fffffffffffffff

using namespace std;
typedef long long ll;
struct edge {
ll to, w, rev;
};
vector<edge> G[V];
int s, t;//源点和汇点是全局变量
bool vis[V];

ll max_flow();
ll dfs(ll u, ll f);
void add_edge(ll from, ll to, ll w);

ll dfs(ll u, ll f) {
if(vis[u]) return 0;
vis[u] = true;
if(u == t) return f;
for(int i = 0; i < G[u].size(); i++) {
edge &e = G[u][i];//注意是引用需要直接修改原图
if(!vis[e.to] && e.w > 0) {
ll curf = dfs(e.to, (f > e.w) ? e.w : f);
//min函数有些编译器不给过
if(curf > 0) {
e.w -= curf;
//直接修改容量而非同时记录f和w只是为了方便
G[e.to][e.rev].w += curf;
return curf;
}
}
}
return 0;
}
ll max_flow() {
ll maxf = 0;
while(true) {
memset(vis, 0, sizeof(vis));
ll f = dfs(s, INF);
if(f == 0) break;
maxf += f;
}
return maxf;
}
void add_edge(ll from, ll to, ll w) {
G[from].push_back(edge(to, G[to].size(), w));
G[to].push_back(edge(from, G[from].size() - 1, 0));
}

如有发现模板的错误请联系我指正我将不胜感激