【最大流】Ford-Fulkerson算法——算法设计与分析


一、问题定义

1.1 流网络

给定有向图 G = < V , E , C > G=<V,E,C> G=<V,E,C>,其被称为流网络:

  • 容量: ∀ e ∈ E , c ( e ) ≥ 0 \forall e \in E, c(e) \ge0 eE,c(e)0
  • 流量: ∀ e ∈ E , 0 ≤ f ( e ) ≤ c ( e ) \forall e \in E, 0 \leq f(e) \leq c(e) eE,0f(e)c(e)
  • 剩余容量: ∀ e ∈ E , c ( e ) − f ( e ) \forall e \in E, c(e)-f(e) eE,c(e)f(e)
  • 总流量: ∣ f ∣ = ∑ e out of s f ( e ) = ∑ e in to t f ( e ) |f|={\textstyle \sum_{\text{e out of s}}f(e) =\textstyle \sum_{\text{e in to t}}f(e) } f=e out of sf(e)=e in to tf(e) (总流量 = 源点流出量 = 汇点流入量)

在这里插入图片描述

1.2 最大流问题

输入:

​ ①有向图 G = < V , E , C > G=<V,E,C> G=<V,E,C>,其中, c ( e ) ∈ C c(e)\in C c(e)C表示边 e e e的容量

​ ②源点 s s s,汇点 t t t


输出:

​ 总流量 ∣ f ∣ |f| f

约束目标: m a x ∣ f ∣ = m a x ∑ e out of s f ( e ) max|f|=max {\textstyle \sum_{\text{e out of s}}f(e)} maxf=maxe out of sf(e)
​ 约束条件:
\quad 容量限制: 0 ≤ f ( e ) ≤ c ( e ) 0\leq f(e)\leq c(e) 0f(e)c(e) (边上的流量不能超过容量)
\quad 流量守恒: ∑ e in to v f ( e ) = ∑ e out of v f ( e ) {\textstyle \sum_{\text{e in to v}}f(e)} = {\textstyle \sum_{\text{e out of v}}f(e)} e in to vf(e)=e out of vf(e) (每个顶点进入和流出的流量和相等)



二、算法策略

2.1 算法引入

直观想法:

①对 ∀ e ∈ E \forall e \in E eE,初始化流量为 f ( e ) = 0 f(e)=0 f(e)=0

②寻找一条源点 s s s到汇点 t t t的路径 P P P,此路径上的每条边 e e e均满足 f ( e ) < c ( e ) f(e)<c(e) f(e)<c(e)

③按路径 P P P上最小剩余容量增加路径流量

④迭代寻找路径 P P P直至无法增加路径流量

例如,在下述所给流网络 G G G中,选择红线路径作为路径 P P P,并增加了11的流量:

在这里插入图片描述

如此迭代寻找 P P P,便可以得到解。

但是,显然该方法并不可以得到最优解,例如,当该流网络为如下状态时,

我们发现,按照直观策略,该网络不能再增加流量。但是如果我们稍作调整,

在这里插入图片描述

如此,我们便成功将网络的流量增加了1。

我们不妨观察一下,我们做了什么样的改动:我们将流量进行了重新分配,缩减了某些边 ( v 2 , v 3 ) (v_2,v_3) (v2,v3)的流量,并将其重新分配到别的边 ( v 2 , v 4 ) (v_2,v_4) (v2,v4)上,实际上,上述变动可以看作我们沿着下图中红色路径走了1个流量:

在这里插入图片描述

也就相当于,我们引入了一条返向边 ( v 3 , v 2 ) (v_3,v_2) (v3,v2),并发现,如果我们寻找路径时允许反向搜索,则可以增大总流量。

一旦引入了反向边,就需要考虑反向边的权重问题,如何定义正向边及反向边的权重以保证流量调整合理呢?做出以下规定:

反向边权重:描述可缩减流量的上限,即原始边上的流量 f ( e ) f(e) f(e)

正向边权重:描述可扩充流量的上限,即原始边上的剩余容量 c ( e ) − f ( e ) c(e)-f(e) c(e)f(e)

如此,由一条原始边可以生成两条正向、反向边,实现反向搜索,以此来帮助我们更好地寻找到使得总流量增加的路径 P P P

2.2 一些概念

接下来介绍几个概念,并按照上述思路,介绍Ford-Fulkerson算法。

2.2.1 残存网络

对于给定流网络 G = < V , E , C > G=<V,E,C> G=<V,E,C>和流量 f f f,可以得到残存网络 G f = < V , E f > G_f=<V,E_f> Gf=<V,Ef>,其中 ∀ c f ( e ) ∈ E f \forall c_f(e) \in E_f cf(e)Ef,其残存容量为:
c f ( e ) = { c ( e ) − f ( e ) e 为正向边 f ( e ) e 为反向边 c_f(e)=\left\{\begin{matrix}c(e)-f(e) & e为正向边\\ f(e)&e为反向边 \end{matrix}\right. cf(e)={c(e)f(e)f(e)e为正向边e为反向边
残存网络中存储的是容量信息(可扩充及可缩减上限),可以帮助我们快速地发现增广路径(包含返向边的路径)。

例如:

在这里插入图片描述

2.2.2 增广路径

给定流网络 G = < V , E , C > G=<V,E,C> G=<V,E,C>和流量 f f f,增广路径 p p p是残存网络 G f G_f Gf中一条从源点 s s s到汇点 t t t的简单路径(路径上顶点不重复)。

例如:

在这里插入图片描述

如果在残存网络中存在增广路径,则说明存在流量的进一步调整方式。

2.2.3 增广路径的残存容量

增广路径的残存容量指的是一条增广路径 p p p上各边残存容量的最小值,即$c_f§=\min\left { c_f(e)|e\in p \right } $

在这里插入图片描述

增广路径的残存容量实际上就是沿着该路径流量扩充的最大值。


2.3 Ford-Fulkerson算法

接下来只需要对我们的只管策略做一些改动,便可以得到Ford-Fulkerson算法:

①对 ∀ e ∈ E \forall e \in E eE,初始化流量为 f ( e ) = 0 f(e)=0 f(e)=0

②构造残存网络 G f G_f Gf,寻找源点 s s s到汇点 t t t的增广路径 p p p

③按增广路径 p p p的残存容量增加路径流量

④迭代寻找路径 p p p直至无法增加路径流量

接下来以一个实例来展示该算法:

①流网络 G G G初始化,构造残存网络 G f G_f Gf

在这里插入图片描述

②在残存网络 G f G_f Gf中找到一条增广路径,并按照其容量更新流网络 G G G

在这里插入图片描述

③更新残存网络 G f G_f Gf

在这里插入图片描述

④重复②③:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

⑤不能找出增广路径 p p p,算法结束:

在这里插入图片描述


2.4 算法分析

该算法的伪代码如下:

在这里插入图片描述

复杂度分析:

  • 初始化阶段: O ( E ) O(E) O(E)

  • 寻找增广路径:残存网络最多有 2 E 2E 2E条边,且 O ( V ) ≤ O ( E ) O(V)\leq O(E) O(V)O(E),使用DFS,可以在 O ( V + 2 E ) = O ( E ) O(V+2E)=O(E) O(V+2E)=O(E)的时间内找到增广路径

  • 找增广路径残存容量: O ( E ) O(E) O(E)

  • 更新流: O ( E ) O(E) O(E)

在这里插入图片描述

每次循环后,流的值至少会增加1,所以循环最多执行 ∣ f ∗ ∣ |f^*| f次,算法的时间复杂度为 O ( E ∣ f ∗ ∣ ) O(E|f*|) O(Ef)

实际上,该算法的运行时间主要受限于增广过程,对于同一个流网络 G G G,不同增广路径的选择,会对结果产生巨大的影响,例如:

在这里插入图片描述

每次循环后,流的值至少会增加1,所以循环最多执行 ∣ f ∗ ∣ |f^*| f次,算法的时间复杂度为 O ( E ∣ f ∗ ∣ ) O(E|f*|) O(Ef)

  • 9
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
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算法中,每次都需要寻找一条增广路来更新流量,直到无法再找到增广路为止。这个过程中,每次找到的增广路都可以使得流量增加,因此最终的流量是不断增加的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

友人帐_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值