c语言最小费用流_初中宝宝也能理解的网络流算法剖析,终于找到葵花宝典了,我太男啦!...

以下主要讲解算法设计,建模案例请见:

黄含驰:网络流算法习题——建模案例​zhuanlan.zhihu.com

全文大意

Ford-Fulkerson算法是解决最大流的常用算法,时间复杂度为C*(m+n) =C* n^2(n是 点数,m是边数),有时候我们凭肉眼观察2次就能轻松找到最大流,但使用Ford-Fulkerson算法却可能要成千上万次,效率太低!
为解决这个问题,选择从源到汇的具有最 少边数的增广路径,即不是通过dfs寻找增广路 径,而是通过 bfs寻找增广路径。 这就是 Edmonds-Karp 最短增广路算法 ,复杂度上限为nm^2.
前面的网络流算法,每进行一次增广,都要做 一遍BFS,十分浪费。 能否少做几次BFS? 这就是Dinic算法要解决的问题。Dinic的DFS找到一条增广路径后, 并不立即结束,而是 回溯后 继续DFS寻找下一个增广路径。(在一次增广的过程中,寻找多条增广路 径 ),时间复杂度是 n*n*m (n是点数,m是边数)
有流量下界的网络最大流的解法与例题
最小费用最大流 的解法与例题

一.解决最大流的Ford-Fulkerson算法

 基本思路很简单,每次用dfs从源到汇找一 条可行路径, 然后把这条路塞满。这条 路径上容量最小的那条边的容量,就是这 次dfs所找到的流量。然后对于路径上的每 条边,其容量要减去刚才找到的流量。 这样,每次dfs都可能增大流量,直到某次 dfs找不到可行路径为止,最大流就求出来 了

这个想法是否正确?

987a941a3643792516d308abd6cb95cd.png

 问题出在过早地认为边a → b上流量不为0,因而“封 锁”了流量继续增大的可能。
 一个改进的思路:应能够修改已建立的流网络,使得 “不合理”的流量被删掉。
 一种实现:对上次dfs时找到的流量路径上的边,添 加一条“反向”边,反向边上的容量等于上次dfs时 找到的该边上的流量,然后再利用“反向”的容量和 其他边上剩余的容量寻找路径。

33236cf5779aae876374f381e92f3d71.png

这样我们第二次dfs搜索的时候就可以在新的网 络里找到新的路径
这是一个取消流的操作 也可以看作是两条路径 的合并

ec322560b425e335fc79e6010d4bc2b3.png

第二次dfs搜索又找到了一个流量为100的流,加 上第一次搜索得到的流量为100的流,总流量上 升到200

afd0a3dd176643492b206ea7b5da3616.png

再对第二次dfs后的图添加反向边,变成

8272e065511cd5ab535f4cfa3ebdc561.png

在此图上再次进行dfs,已经找不到路径了,所以 流量无法再增加,最大流就是200

残余网络 (Residual Network)

在一个网络流图上,找到一条源到汇的路径 (即找到了一个流量)后,对路径上所有的 边,其容量都减去此次找到的流量,对路径 上所有的边,都添加一条反向边,其容量也 等于此次找到的流量,这样得到的新图,就 称为原图的“残余网络“。

为什么添加反向边(取消流)是有效的?

见http://acm.pku.edu.cn/summerschool/gw_netflow.pdf p12-p17

Ford-Fulkerson算法

求最大流的过程,就是不断找到一条源到汇 的路径,然后构建残余网络,再在残余网络 上寻找新的路径,使总流量增加,然后形成 新的残余网络,再寻找新路径…..直到某个 残余网络上找不到从源到汇的路径为止,最 大流就算出来了。
每次寻找新流量并构造新残余网络的过程, 就叫做寻找流量的“增广路径”,也叫“增 广”

现在假设每条边的容量都是整数
这个算法每次都能将流至少增加1
由于整个网络的流量最多不超过 图中所有的边的容 量和C,从而算法会结束
现在来看复杂度
找增广路径的算法可以用dfs, 复杂度为边数m+顶 点数n
Dfs 最多运行C次
所以时间复杂度为C*(m+n) =C* n^2

58ce3aefcbeb2b8e8a2f076e8b421531.png

二.Edmonds-Karp 最短增广路算法

FF算法实现很简单 但是注意到在图中C可能很大很大 比如说下面这张图

1ab33a30560cf8feb8305ca3a299d68c.png

如果运气不好 这种图会让你的程序执行200次dfs 虽然实际上最少只要2次我们就能得到最大流

如何避免上述的情况发生?

在每次增广的时候,选择从源到汇的具有最 少边数的增广路径,即不是通过dfs寻找增广路 径,而是通过bfs寻找增广路径。 这就是Edmonds-Karp 最短增广路算法

已经证明这种算法的复杂度上限为nm^2 (n是 点数,m是边数)

Poj 1273 Drainage Ditches 赤裸裸的网络流题目。给定点数,边数,每条 边的容量,以及源点,汇点,求最大流。

b515ab7a2a31a5ca85b94223275f2ad6.png
#include <iostream>
#include <queue>
using namespace std;
int G[300][300];
int Prev[300]; //路径上每个节点的前驱节点
bool Visited[300];
int n,m; //m是顶点数目,顶点编号从1开始 1是源,m是汇, n是
边数
unsigned Augment()
{
int v;
int i;
deque<int> q;
memset(Prev,0,sizeof(Prev));
memset(Visited,0,sizeof(Visited));
Prev[1] = 0;
Visited[1] = 1;
q.push_back(1);
bool bFindPath = false;
//用bfs寻找一条源到汇的可行路径
while( ! q.empty()) {
v = q.front();
q.pop_front();
for( i = 1;i <= m;i ++) {
if( G[v][i] > 0 && Visited[i] == 0) {
//必须是依然有容量的边,才可以走
Prev[i] = v;
Visited[i] = 1;
if( i == m ) {
bFindPath = true;
q.clear();
break;
}
else
q.push_back(i);
}
}
}
if( ! bFindPath)
return 0;
int nMinFlow = 999999999;
v = m;
//寻找源到汇路径上容量最小的边,其容量就是此次增
加的总流量
while( Prev[v] ) {
nMinFlow = min( nMinFlow,G[Prev[v]][v]);
v = Prev[v];
}
//沿此路径添加反向边,同时修改路径上每条边的容量
v = m;
while( Prev[v] ) {
G[Prev[v]][v] -= nMinFlow;
G[v][Prev[v]] += nMinFlow;
v = Prev[v];
}
return nMinFlow;
}
int main()
{
while (cin >> n >> m ) {
//m是顶点数目,顶点编号从1开始
int i,j,k;
int s,e,c;
memset( G,0,sizeof(G));
for( i = 0;i < n;i ++ ) {
cin >> s >> e >> c;
G[s][e] += c; //两点之间可能有多条边
}
unsigned int MaxFlow = 0;
unsigned int aug;
while( aug = Augment() )
MaxFlow += aug;
cout << MaxFlow << endl;
}
return 0;
}

三.Dinic 快速网络流算法

前面的网络流算法,每进行一次增广,都要做 一遍BFS,十分浪费。能否少做几次BFS? 这就是Dinic算法要解决的问题

Dinic 算法

 Edmonds-Karp的提高余地:需要多次从s到t调 用BFS,可以设法减少调用次数。 亦即:使用一种代价较小的高效增广方法。
 考虑:在一次增广的过程中,寻找多条增广路 径。
 DFS

 先利用 BFS对残余网络分层。一个节点的“层”数,就是源点到它最少要经过的边数。

ef02d080c011de2a846a5d07892e2567.png

分完层后,从源点开始,用DFS从前一层向后一层反复 寻找增广路(即要求DFS的每一步都必须要走到下一层 的节点)。
因此,前面在分层时,只要进行到汇点的层次数被算出 即可停止,因为按照该DFS的规则,和汇点同层或更 下一层的节点,是不可能走到汇点的。
DFS过程中,要是碰到了汇点,则说明找到了一条增广 路径。此时要增加总流量的值,消减路径上各边的容 量,并添加反向边,即所谓的进行增广。

DFS找到一条增广路径后,并不立即结束,而是回溯后 继续DFS寻找下一个增广路径。

回溯到哪个节点呢?
回溯到的节点u满足以下条件:
1) DFS搜索树的树边(u,v)上的容量已经变成0。即刚刚找 到的增广路径上所增加的流量,等于(u,v)本次增广前的 容量。(DFS的过程中,是从u走到更下层的v的)
2)u是满足条件 1)的最上层的节点

如果回溯到源点而且无法继续往下走了,DFS结束。
因此,一次DFS过程中,可以找到多条增广路径。
DFS结束后,对残余网络再次进行分层,然后再进行DFS
残余网络的分层操作无法算出汇点的层次(即BFS到达 不了汇点)时,算法结束,最大流求出。
一般用栈实现DFS,这样就能从栈中提取出增广路径Dinic 复杂度是 n*n*m (n是点数,m是边数)

要求出最大流中每条边的流量,怎么办?
将原图备份,原图上的边的容量减去做完最大 流的残余网络上的边的剩余容量,就是边的 流量

#include <iostream> #include <queue> #include <vector> #include <algorithm> #include <deque> using namespace std;
#define INFINITE 999999999 //Poj 1273 Drainage Ditches 的 Dinic算法
int G[300][300];
bool Visited[300];
int Layer[300]; int n,m; //1是源点,m是汇点
bool CountLayer() {
int layer = 0; deque<int>q;
memset(Layer,0xff,sizeof(Layer)); //都初始化成-1
Layer[1] = 0; q.push_back(1);
while( ! q.empty()) {
int v = q.front();
q.pop_front();
for( int j = 1; j <= m; j ++ ) {
if( G[v][j] > 0 && Layer[j] == -1 ) {
//Layer[j] == -1 说明j还没有访问过
Layer[j] = Layer[v] + 1;
if( j == m ) //分层到汇点即可
return true;
else
q.push_back(j);
}
}
}
return false;
}
int Dinic()
{
int i; int s;
int nMaxFlow = 0;
deque<int> q; //DFS用的栈
while( CountLayer() ) { //只要能分层
q.push_back(1); //源点入栈
memset(Visited,0,sizeof(Visited)); Visited[1] = 1;
while( !q.empty()) {
int nd = q.back();
if( nd == m ) { // nd是汇点
//在栈中找容量最小边
int nMinC = INFINITE;
int nMinC_vs; //容量最小边的起点
for( i = 1;i < q.size(); i ++ ) {
int vs = q[i-1];
int ve = q[i];
if( G[vs][ve] > 0 ) {
if( nMinC > G[vs][ve] ) {
nMinC = G[vs][ve];
nMinC_vs = vs;
}
}
}
//增广,改图
nMaxFlow += nMinC;
for( i = 1;i < q.size(); i ++ ) {
int vs = q[i-1];
int ve = q[i];
G[vs][ve] -= nMinC; //修改边容量
G[ve][vs] += nMinC; //添加反向边
}
//退栈到 nMinC_vs成为栈顶,以便继续dfs
while( !q.empty() && q.back() != nMinC_vs ) {
Visited[q.back()] = 0; //没有这个应该也对
q.pop_back();
}
}
else { //nd不是汇点
for( i = 1;i <= m; i ++ ) {
 if( G[nd][i] > 0 && Layer[i] == Layer[nd] + 1 &&
 ! Visited[i]) {
//只往下一层的没有走过的节点走
Visited[i] = 1;
q.push_back(i);
break;
}
}
if( i > m) //找不到下一个点
q.pop_back(); //回溯
}
}
}
return nMaxFlow;
}
int main()
{
while (cin >> n >> m ) {
int i,j,k;
int s,e,c;
memset( G,0,sizeof(G));
for( i = 0;i < n;i ++ ) {
cin >> s >> e >> c;
G[s][e] += c; //两点之间可能有多条边
}
cout << Dinic() << endl;
}
return 0;
}

四.有流量下界的网络最大流

 如果流网络中每条边e对应两个数字B(e)和C(e), 分别表示该边上的流量至少要是B(e),最多 C(e),那么,在这样的流网络上求最大流,就是 有下界的最大流问题。
 这种网络不一定存在可行流,如:

f10b054ec765d8b851e28bfec45e8667.png

8ce937d7e3addef5d8449fb99246de80.png

 思路:将下界“分离”出去,使问题转换为下 界为0的普通网络流问题。

e89f7036fcb36ae31b669ca5715bed57.png

由于必要弧的有一定要满流的限制,将必要弧“拉” 出来集中考虑:

添加附加点x,y。想像一条不限上界的(x, y),用必 要弧将它们“串”起来,即对于有向必要弧(u, v),添加 (u, x),(y, v),容量为必要弧容量。这样就建立了一个等 价的网络。

febfaa1ac490e3fddc47e5f5116910c8.png

去掉边(x,y),添加由t到s的容量为正无穷大的边,使y和 x分别成为新的源和新的汇。

若此图上的最大流能够占满与Y相连的所有边的容量(自 然也就会占满所有连到x的边的容量),那么原图上就存 在满足上下界条件的可行流。若最大流不能够占满与Y相 连的所有边的容量,则原图不存在可行流。

b96aa94f89eb473b5f9fa50568657302.png

新图最大流若小于新图中x的流入量之和,则原问题 无解 在新图的最大流中,求出s流出的流量之和,记为 sum1

在做过一遍最大流的新图的残余网络中,去掉t->s 以及s->t的边,然后以s为源,t为汇再做一次最大流, 此时得到的流量 sum2,则 sum1+sum2就是在原图 上满足下界的最大流。

和x,y相连的边不用处理,因为x,y实际上是只能流入 或只能流出的点,在图中不起作用。

要想求出每条边上的流量,怎么办?

在做第二次最大流之前,将图备份到G2
经过两次求最大流后,最后变成的残余网络是G
此时G2[i][j] – G[i][j] + LC[i][j] 就是 i->j上的流量
LC[i][j] 是i->j边上的流量下界(下界是被满足的)

处理网络流题目要注意,如果有重边,则要将重边 上的容量和下界累加,合并成一条边。

POj 2396 Budget
参考: http://blog.csdn.net/wsniyufang/article/details/6601162
现在有一个n*m的方阵,方阵里面的数字未知,但是我们 知道如下约束条件:
1> 每一行的数字的和 2> 每一列的数字的和 3> 某些格子里的数,大小有限制。比如规定第2 行第3列的数字必须大于5(或必须小于3,或必须等于10 等)
求解是否存在在满足所有的约束的条件下用正数来填 充该方阵的方案,若有,输出填充后的方阵,否则输出 IMPOSSIBLE.

这道题可以转化成容量有上下界的最大流问题,将方阵的行 从1……n编号,列n+1……n+m编号,添加源点s=0和汇点 t=n+m+1.
1>将源点和每一个行节点相连,相连所形成的边的 容量和下界置为该行所有数字的和
2>将每一个列节点和汇点相连,相连所形成的边的 容量和下界都置为该列所有数字的和
3>从每个行节点到每个列节点连边,容量为无穷大
4> 如果u行v列的数字必须大于w,则边流 量的下界是w+1
5> 如果u行v列的数字必须小于w,则边容 量改为w-1
6> 如果u行v列的数字必须等于w,则边流 量的下界和容量都是w 找到的可行流(也是最大流),就是问题的解

六.最小费用最大流

以下内容引自http://web.nuist.edu.cn/courses/dlxxxt/ch5/5.7.3.htm http://jpkc.lzjtu.edu.cn/material3/xxxt/zxfyzdl.htm

设有一个网络图G(V,E),,V={s,a,b,c,…,s’},E中 的每条边(i,j)对应一个容量c(i,j)与输送单位流量所需费用 a(i,j)。如有一个运输方案(可行流),流量为f(i,j),则 最小费用最大流问题就是这样一个求极值问题:

e1c54174a1782051856ffe15a57a5892.png

其中F为G的最大流的集合,即在最大流中寻找一个费用 最小的最大流。

最小费用最大流算法过程

256653ef44e063dc4e0b0d15809c8e0d.png

反复用spfa算法做源到汇的最短路进行增广,边 权值为边上单位费用。反向边上的单位费用是负 的。
直到无法增广,即为找到最小费用最大流。
成立原因:每次增广时,每增加1个流量,所增 加的费用都是最小的。
因为有负权边(取消流的时候产生的),所以不 能用迪杰斯特拉算法求最短路。

POJ 2135

 题目描述:
 有n个景点,一个人要从1号景点走到n号景点,再 从n号景点走到1号(回来的路不能重复,不一定走 完所有景点,只要求从1到n即可),给你一些景点 之间的路的长度(双向),问你最短需要走多少路 才能回来?
 解题报告:
 最小费用就是路径长度的总和,最大流就是来回的 两条路。
 由于去和回来可以看成:2条从1到n的不同的路。 所以转化成求从1到n的两条不同的路。  假设a b之间有长度为c的路。按照最小费用流建 图:
 ab之间费用为c,容量是1。
 ba之间费用为c,容量是1.
 建立一个源点,连接1号景点,无费用,容量为2 (表示可以有两条路)
 同理,建立一个汇点,连接n号景点,无费用,容 量为2.
 这样,如果求的的最大流是2,就表示了有两条从 1到n的不同的路。(因为中间的点边容量只是1, 只能用一次)
 这样求的最小费用最大流的最小费用就是最短路 径长度。

参考资料:

http://staff.ustc.edu.cn/~xiaomj/teaching/sf08.pdf

https://www.cnblogs.com/liu-runda/p/6262832.html

http://old.orzsiyuan.com/articles/algorithm-Network-Flow-Maximum-Flow/

http://acm.pku.edu.cn/summerschool/gw_netflow.pdf

最直白的网络流课件https://wenku.baidu.com/view/74

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值