Ford-Fulkerson算法——最大流、最小割问题

流网络

       网络流G=(v, E)是一个有向图,其中每条边(u, v)均有一个非负的容量值,记为c(u, v) ≧ 0。如果(u, v) ∉ E则可以规定c(u, v) = 0。网络流中有两个特殊的顶点,即源点s和汇点t。

 流的定义

与网络流相关的一个概念是流。设G是一个流网络,其容量为c。设s为网络的源点(source),t为汇点(sink),那么G的流是一个函数f:V×V →R,满足以下性质:

  • 容量限制: 对每条边e ∈ E :0<=f(e)<=c(e)
  • 流守恒性:对每个顶点v ∈ V-{s,t}: \sum_{e\ into\ v}f(e)=\sum_{e\ outof\ v}f(e)

定义:流的值为:v(f)=\ \sum_{e\ outof\ s}f(e)

如图:

最大流问题

就是在容量容许的条件下,从源点到汇点所能通过的最大流量

在这之前,先看几个定义。

增广路径

 定义:增广路径是找出在残留网络中从源点到汇点的有向路径。增广路径的残留容量是路径中任意边所形成的最小残留容量。显然,我们可以沿着增广路径从源点到汇点发送额外的流。

左图是贪心算法求出的一条路径,右图是在此基础上找增广路径,红色虚线部分为增广路径

通过寻找增广路径,我们可以迭代的增加流的值

如何寻找增广路径?

在残量图中找

残量图

定义:

  • 原边:e=(u,v)∈E,有流 f(e),容量 c(e)

残量边:

  • 前向边:e=(u,v)∈E,当f(e)<c(e)
  • 反向边:e^R=(u,v),当f(e)>0
  • 残量容量:

残量图:G_f=(V,E_f)

  • 残量边有正的残余容量
  • E_f=\left \{ e:f(e)<c(e) \right \}\bigcup \left \{ e^R:f(e)>0 \right \}

Ford-Fulkerson算法伪代码

bottleneck是一条路径中最小的那个容量 

构造残量图不用真的构造,有trick(俺也不知道,等俺想到了补上) 

Augment(f,c,P)
    b=bottleneck(P)
    for each e ∈ P
        if (e∈E) f(e)=f(e)+b
        else f(eR)=f(e)-b
    return f

Ford-Fulkerson(G,c,s,t)
{
    for each e∈E f(e)=0
    Gf= residual graph

    while(there exists augmenting path P in Gf)
        f=Augment(f,c,P)
        update Gf
    
    return f
}

证明算法正确性

算法中while循环是否会终止

  • 假定:所有的容量都是整数
  • 声明1:f是G中的一个流,P是G_f(残量图)中s到t的一条简单路径。则

v(f')=v(f)+bottleneck(P,f)

并且只要bottleneck(P,f)>0,则有v(f')>v(f)

  • 声明2:Ford-Fulkerson算法的while循环最多终止在C(\sum _{e\ out of\ s}c_e),即s输出的容量和,不可能更大,但可能更小

实数可以作为容量吗?

在实际应用中,容量一般是整数和有理数。如果容量带小数,可能会遇到循环多次,每次流只增加一点点,循环很难结束的情况

 证明算出来的最大流是正确的

引入割的概念

割(cuts)

定义:是网络中顶点的一个划分,把所有顶点划分成两个顶点集合A和B,其中源点s属于A,汇点t属于B,记作(A,B)

割的容量的定义:所有正向割边的容量和,不同割的容量不同,即cap(A,B)=\sum _{e\ outof\ A}c(e)

图中即为一个割

最小割问题

即为求一个最小容量的割

证明求最小割和求最大流是一样的

流值引理(Flow value lemma)

设f是任意s到t的流,(A,B)是任意s到t的割,则经过割的净流量等于s输出的流量

如图,三个割的净流量都是等于s输出的流量24

证明:

弱对偶性(Weak duality)

设f是任意s到t的流,(A,B)是任意s到t的割,则流的最大值最多能到割的容量,即v(f)<=cap(A,B)

证明:

最佳选择证明

推论:设f是任意s到t的流,(A,B)是任意s到t的割,如果v(f)=cap(A,B),则f是最大流,(A,B)是最小割

最大流最小割定理

最大流的值等于最小割的值

增广路径定理

f是s到t的最大流当且仅当没有增广路径时

证明策略:这三个性质可以相互推导

(1)存在一个割(A,B)满足v(f)=cap(A,B)

(2)流f是最大流

(3)没有与f有关的增广路径

(1)推导(2):弱对偶性的推论(不懂……)

(2)推导(3):反证法:如果f存在增广路径,则f还可以增长,则f不是最大流

(3)推导(1):

构造性证明,对条件(3),求出割(A,B)满足v(f)=cap(A,B)

设流f没有增广路径

设A是残量图G_f中从源点s出发的一系列可达的顶点的集合

 进入A的边f(e)=0,从A出来的边的f(e)等于c(e)

反推:

如果一条边进入A且f(e)不为0,那么这条边的起点应该在A里

如果一条边从A出来且f(e)不等于c(e),那么这条边的终点也在A里(俺也没明白,到底啥意思啊……)

如何求最小割

  • 那么如何来求出最小割的其中一种方案呢,我们在进行最大流计算完成之后,从原点进行遍历整张图,将遍历到的点标记为 true ;最终结束之后,所有标记为 true 的点和没有标记的点之间就是一个割边。

  • 那么为什么求出来的就是割边呢,因为最大流之后,所有通路在满流的那条通道处断掉了,也就是没有办法继续走下去,而这条通道一边标记为了 true 另一边没有被标记,那么他们之间就是一个割边了。

 时间复杂度

  • 完整性定理:如果所有的容量都是整数,那么最大流也是整数

证明很简单,因为每次求路径p的瓶颈肯定是整数,求残量图的容量也是整数,再求增广路径上的容量也还是整数,所以最大流一定是整数

  • Ford-Fulkerson算法的时间复杂度取决于增广路径如何决定
  • 如果所有的容量都是整数,有一个Ford-Fulkerson算法的直接实现的时间复杂度O(|E||f*|),其中f*由算法决定
  • Edmonds Karp 算法是 Ford Fulkerson 方法的一种具体实现。它采用 BFS 搜索方法在剩余网络中寻找一条增广路径。具体做法是先对每条边的权值统一赋值为 1 ,然后找一条从 s 到 t的最短路径。基于此方法, Edmonds Karp 算法求最大流的时间为 O(VE^2)
  • 13
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Ford-Fulkerson算法是求解最大流问题的一种经典算法。以下是一个基于增广路思想的Ford-Fulkerson算法的代码: ``` // 基于邻接矩阵的Ford-Fulkerson算法实现 #include <iostream> #include <queue> #include <cstring> using namespace std; const int MAXN = 100; // 最大顶点数 const int INF = 0x3f3f3f3f; // 表示无穷大 int n, m; // n表示顶点数,m表示边数 int s, t; // s表示源点,t表示汇点 int cap[MAXN][MAXN]; // 表示容量 int flow[MAXN][MAXN]; // 表示量 int pre[MAXN]; // 表示前驱节点 int bfs() { memset(pre, -1, sizeof(pre)); // 初始化前驱节点数组 queue<int> q; q.push(s); pre[s] = -2; while (!q.empty()) { int u = q.front(); q.pop(); for (int v = 0; v < n; ++v) { if (pre[v] == -1 && cap[u][v] > flow[u][v]) { pre[v] = u; if (v == t) return 1; q.push(v); } } } return 0; } int maxFlow() { int ans = 0; while (bfs()) { int minflow = INF; for (int u = t; u != s; u = pre[u]) { int v = pre[u]; minflow = min(minflow, cap[v][u] - flow[v][u]); } for (int u = t; u != s; u = pre[u]) { int v = pre[u]; flow[v][u] += minflow; flow[u][v] -= minflow; } ans += minflow; } return ans; } int main() { cin >> n >> m >> s >> t; memset(cap, 0, sizeof(cap)); memset(flow, 0, sizeof(flow)); for (int i = 0; i < m; ++i) { int u, v, c; cin >> u >> v >> c; cap[u][v] += c; // 注意有可能存在重边 } cout << maxFlow() << endl; return 0; } ``` 算法思路: 1. 初始化量为0; 2. 在剩余容量大于0的情况下,寻找增广路: - 从源点s开始,使用BFS寻找一条增广路; - 如果找到增广路,计算增广路上的最小剩余容量minflow,更新量; 3. 最大流就是所有增广路上的最小剩余容量之和。 其中,增广路的定义是指从源点到汇点路径上,剩余容量均大于0的路径。在Ford-Fulkerson算法中,每次都需要寻找一条增广路来更新量,直到无法再找到增广路为止。这个过程中,每次找到的增广路都可以使得量增加,因此最终的量是不断增加的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值