最大流算法之Ford-Fulkerson算法与Edmonds–Karp算法
引子
曾经很多次看过最大流的模板,基础概念什么的也看了很多遍。也曾经用过强者同学的板子,然而却一直不会网络流。虽然曾经尝试过写,然而即使最简单的一种算法也没有写成功过,然后对着强者大神的代码一点一点的照猫画虎,A了一题。然而这并没有什么用,实际上我还是不会呀。过一阵子就写不出来了,所以那个时候的A应该就是对照着换了换变量吧。持续性萎靡不振,间歇性踌躇满志的我觉得是时候不看资料尤其是不看他人代码完全的自己写一道模板题了。
题目
hihocoder 1369 http://hihocoder.com/problemset/problem/1369
hdu 3549 http://acm.hdu.edu.cn/showproblem.php?pid=3549
两道数据范围极小的模板练习题
最大流问题
不想过多的重复网上已有的基础概念。只想叙述一下我的理解。
把边都想象成仅可以单向流动的水管(虽然这有点奇怪,或者换成单向车道没那么奇怪)。问题就是求从源点到汇点最多流多少水。
流量网络就是每条边流多少水构成的图。
显然通过每条边的限制不能超过该边的容量限制。
残余网络就是每条边还可以流多少水。
最为重要的是反向边的理解了。
当我选择对于在边(u,v)" role="presentation" style="position: relative;">(u,v)(u,v)增加其一个流量f" role="presentation" style="position: relative;">ff,那么u到v的新的残余容量就是这个操作之前残余流量减去f" role="presentation" style="position: relative;">ff,即
同时增加对v到u的残余流量(如果一开始v到u没有边就是容量为0)增加f.
这个容量的增加如何理解呢?
如果选择给v到u一个f′" role="presentation" style="position: relative;">f′f′的流量,相当于原本变化前的图中u到v的f" role="presentation" style="position: relative;">ff这个流量少流f′" role="presentation" style="position: relative;">f′f′
只要f′≤f" role="presentation" style="position: relative;">f′≤ff′≤f,我们都可以在原图中通过少流的方式构造出等价的实现,所以我们当我们给e(u,v)" role="presentation" style="position: relative;">e(u,v)e(u,v)增加一个f" role="presentation" style="position: relative;">ff的流量的同时,同时给e(v,u)" role="presentation" style="position: relative;">e(v,u)e(v,u)增加一个f" role="presentation" style="position: relative;">ff的容量,避免我们先陷入后面发现e(u,v)" role="presentation" style="position: relative;">e(u,v)e(u,v)流太多了,需要应该少流一点点,却不知道怎么写判断,怎么回溯,怎么后悔的尴尬境地。有了反向边,一个无须判断过多讨论,多流了的情况自然的蕴含在找可行流的时候通过给边e(v,u)" role="presentation" style="position: relative;">e(v,u)e(v,u)一个流而把多流的流量流回去。
Ford-Fulkerson算法
下面说说Ford-Fulkerson算法
寻找问题的解的时候是基于残留网络。
初始的残留网络就是容量网络。
Ford-Fulkerson算法是不断的在当前的残留网络中找一条从源点到汇点的路径,这条路径上的每个容量值需要大于0。如果有这样的一条路径path(s,t)" role="presentation" style="position: relative;">path(s,t)path(s,t),那么记flow=min{cei},ei∈path(s,t)" role="presentation" style="position: relative;">flow=min{cei},ei∈path(s,t)flow=min{cei},ei∈path(s,t),显然可以给路径上的每条边一个flow的流量。来增加我们的可行流的总量。因此,这样的路径称之为增广路。
由于我们是基于残留网络求解,同时要增加反向边。我们直接给出对残留网络的修改:
路径上的每条边e(u,v)" role="presentation" style="position: relative;">e(u,v)e(u,v)的残余流量减少flow" role="presentation" style="position: relative;">flowflow,同时e(v,u)" role="presentation" style="position: relative;">e(v,u)e(v,u)的残余流量加上flow" role="presentation" style="position: relative;">flowflow
Fold-Fulkerson算法就是不断的在残余网络中找增广路,然后更新残留网络,直到找不到增广路为止。当找不到时,我们就已经得到最大流了。
至于时间复杂度,如果边的容量是无理数的话,Ford-Fulkerson算法甚至可能一直运行下去无法结束。但是如果容量值是整数,那么由于找到一条增广路的时间最多是O(E)" role="presentation" style="position: relative;">O(E)O(E),每次找到的增广路的流量值至少是1,那么时间复杂度应该是O(Ef)" role="presentation" style="position: relative;">O(Ef)O(Ef).其中f" role="presentation" style="position: relative;">ff是最后最大流问题的解。具有保证终止和且运行时间与最大流量值无关的的的Ford-Fulkerson算法的变体是Edmonds–Karp算法。
以上这段参考了维基百科。如果有理解错误的地方希望大家指出。
Edmonds–Karp算法
如上所述,Edmonds–Karp算法可以说是一种Ford-Fulkerson算法的具体化,或者说是一种变体。Ford-Fulkerson算法并没有具体叙述如何找增广路,所以可以采用dfs,bfs。而Edmonds–Karp算法大体与Ford-Fulkerson算法相同,只是要求找出的最短路是当前残留网络中边数最小的路径。即找一条最短路(边数的最短)。其寻找最短路的方式是采用BFS。
其时间复杂度是:
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
int n,m;
int pointCount,edgeCount;
const int kMaxPointCount = 500;
const int kMaxInt = 0x7fffffff;
typedef map<int,int> toVCapacity; // 使用map,隐含:不能有重边,因此对于重边直接容量合并
toVCapacity edgeFrom[kMaxPointCount+2]; // 下标从0开始
// hihocoder 1369
// hdu 3549
// reverseWay应当保存的是反过来的增广路径的节点,不包括汇点end
// 大概这种形式 end-v1-v2-v3-v4-v5-...-start (边是反过来的,(v1,end)是一条边) 保存的节点不包含end
bool getAWayByBfs(int pointCount,int edgeCount,int start,int end,toVCapacity remainCapacityFrom[],int pre[],vector<int>&reverseWay,int flows[])
{
const int notVisited = -1;/*-1 一个特殊数字,表示还没有访问,由于要使用memset,不可替换成其它数字*/
const int noPre = -2; /*一个标记没有前置节点的特殊数字而已*/
reverseWay.clear();
memset(pre,notVisited,sizeof(int)*pointCount);
queue<int>q;
flows[start] = kMaxInt; pre[start] = noPre; q.push(start);
int u,v,remainCapacity;
toVCapacity::iterator edgeIt,tmpEndEdgeIt;
bool reachEndFlag = false;
while ((!reachEndFlag) && (!q.empty())) {
u = q.front(); q.pop();
edgeIt = remainCapacityFrom[u].begin();
tmpEndEdgeIt = remainCapacityFrom[u].end();
while (edgeIt != tmpEndEdgeIt) {
v = edgeIt->first;
remainCapacity = edgeIt->second;
if (notVisited == pre[v] && remainCapacity > 0) {
flows[v] = min(flows[u],remainCapacity);
pre[v] = u; q.push(v);
if (v == end) {
reachEndFlag = true;
break;
}
}
++edgeIt;
}
}
if (pre[end] == notVisited)
return false;
v = end;
while (v != start) {
//cout<<v<<" ";
v = pre[v];
reverseWay.push_back(v);
}
//cout<<"end = "<<end<<"flow = "<<flows[end]<<" "<<"\n";
return true;
}
void addCapacity(toVCapacity remainCapacityFrom[],int u,int v,int addition)
{
toVCapacity::iterator it;
it = remainCapacityFrom[u].find(v);
if (it == remainCapacityFrom[u].end()) {
if (addition > 0) {
remainCapacityFrom[u].insert(pair<int,int>(v,addition));
} else if (addition < 0) {
//cout<<"capacity error"<<"\n";
//exit(0);
} // == 0, ignore
} else {
it->second += addition;
if (it->second == 0) {
remainCapacityFrom[u].erase(it);
} else if (it->second < 0) {
//cout<<"capacity error"<<"\n";
//exit(0);
} // else ... ignore
}
}
int getMaxFlow(int pointCount,int edgeCount,int start,int end,toVCapacity remainCapacityFrom[])
{
int maxFlow = 0;
int flows[pointCount+2],flow;
int pre[pointCount+2];
vector<int>reverseWay;
vector<int>::iterator way_it;
int u,v;
while (1) {
// find a path
// if can,then we modify,or we break out
if (!getAWayByBfs(pointCount,edgeCount,start,end,remainCapacityFrom,pre,reverseWay,flows))
break;
// reverseWay保存的是反过来的增广路径的节点,不包括汇点end
flow = flows[end];
v = end; way_it = reverseWay.begin();
while (way_it != reverseWay.end()) {
u = *way_it;
addCapacity(remainCapacityFrom,u,v,-flow);
addCapacity(remainCapacityFrom,v,u,flow);
v = u; ++way_it;
}
maxFlow += flow;
}
return maxFlow;
}
int main1()
{
char ln = '\n';
cin>>n>>m; // 点数 边数
pointCount = n; edgeCount = 2*m;
int u,v,c;
// 输入数据的点的下标是从1开始,源点为1,汇点为n
for (int i = 0; i< pointCount; ++i)
edgeFrom[i].clear();
for (int i = 0;i < m; ++i) {
cin>>u>>v>>c;
--u,--v;
//edgeFrom[u].insert(pair<int,int>(v,c));
//上面这样写当有重边的时候会导致错误,因为假设
// 1 2 3
// 1 2 6
// 加进去的边只有1 2 3 第二次 1 2 6的时候的insert是无效的,不执行的
// 如果改成下标方式的插入元素,则第一次加进去的1 2 3将会被覆盖掉所以直接调用上面写
// 因为这个wa了好久
// 因此加边还需要判断原本是否有边的存在,懒得写判断,就直接使用原本修改的时候用的addCapacity函数了
addCapacity(edgeFrom,u,v,c);
}
int ans = getMaxFlow(pointCount,edgeCount,0,pointCount-1,edgeFrom);
cout<<ans<<ln;
return 0;
}
int main()
{
// for hdu
/*int T;
std::ios::sync_with_stdio(false);
cin.tie(0);
cin>>T;
for (int i = 1; i <= T; ++i) {
cout<<"Case "<<i<<": ";
main1();
}*/
//for hihocoder
main1();
return 0;
}
最大流算法之Ford-Fulkerson算法与Edmonds–Karp算法的更多相关文章
- 网络流(一)——Edmonds Karp算法
首先是一些关于网络流的术语: 源点:即图的起点. 汇点:即图的终点. 容量:有向边(u,v)允许通过的最大流量. 增广路:一条合法的从源点流向汇点的路径. 网络流问题是在图上进行解决的,我们通常可以将 ...
- ACM/ICPC 之 网络流入门-Ford Fulkerson与SAP算法(POJ1149-POJ1273)
第一题:按顾客访问猪圈的顺序依次构图(顾客为结点),汇点->第一个顾客->第二个顾客->...->汇点 //第一道网络流 //Ford-Fulkerson //Time:47M ...
- 国密SM4对称算法实现说明(原SMS4无线局域网算法标准)
国密SM4对称算法实现说明(原SMS4无线局域网算法标准) SM4分组密码算法,原名SMS4,国家密码管理局于2012年3月21日发布:http://www.oscca.gov.cn/News/201 ...
- 从K近邻算法谈到KD树、SIFT+BBF算法
转自 http://blog.csdn.net/v_july_v/article/details/8203674 ,感谢july的辛勤劳动 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章 ...
- 经典算法题每日演练——第十七题 Dijkstra算法
原文:经典算法题每日演练--第十七题 Dijkstra算法 或许在生活中,经常会碰到针对某一个问题,在众多的限制条件下,如何去寻找一个最优解?可能大家想到了很多诸如“线性规划”,“动态规划” 这些经典 ...
- 经典算法题每日演练——第十一题 Bitmap算法
原文:经典算法题每日演练--第十一题 Bitmap算法 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场 ...
- 经典算法题每日演练——第七题 KMP算法
原文:经典算法题每日演练--第七题 KMP算法 在大学的时候,应该在数据结构里面都看过kmp算法吧,不知道有多少老师对该算法是一笔带过的,至少我们以前是的, 确实kmp算法还是有点饶人的,如果说红黑树 ...
- hdu2389二分图之Hopcroft Karp算法
You're giving a party in the garden of your villa by the sea. The party is a huge success, and every ...
- kNN算法:K最近邻(kNN,k-NearestNeighbor)分类算法
一.KNN算法概述 邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一.所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它 ...
随机推荐
- Nginx 主要应用场景
前言 本文只针对 Nginx 在不加载第三方模块的情况能处理哪些事情,由于第三方模块太多所以也介绍不完,当然本文本身也可能介绍的不完整,毕竟只是我个人使用过和了解到过得.所以还请见谅,同时欢迎留言交流 ...
- Linux系统下常见的数据盘分区丢失的问题以及对应的处理方法
在修复数据前,您必须先对分区丢失的数据盘创建快照,在快照创建完成后再尝试修复.如果在修复过程中出现问题,您可以通过快照回滚将数据盘还原到修复之前的状态. 前提条件 在修复数据前,您必须先对分区丢失的数 ...
- 一个故事看懂Linux文件权限管理
前情回顾: 我通过open这个系统调用虫洞来到了内核空间,又在老爷爷的指点下来到了sys_open的地盘,即将开始打开文件的工作. 详情参见:内核地址空间大冒险:系统调用 open系统调用链 我是一个 ...
- 在centos7.x环境中SQL Server附加数据库
第一步,准备好windows与Linux之间文件传递的工具,下载并安装 https://winscp.net/eng/download.php 第二步,把本地的数据库文件拷贝一份,放到别的文件夹中,因 ...
- Ubuntu系统下使用php7+mysql+apache2搭建自己的博客
很多人都有写博客的习惯,奈何国内的博客网站正在一家家地关闭与重整,部分博客网站也充斥着太多的广告,使用体验非常不好.对于爱写博客的朋友来说,其实还有一个更好的选择,那就是自己搭建一个博客. 搭建一个自 ...
- Learning hard 学习笔记
第一章 你真的了解C#吗 1.什么是C#, 微软公司,面向对象,运行于.NET Framework之上, 2.C#能编写哪些应用程序, Windows应用桌面程序,Web应用程序,Web服务, 3.什 ...
- Windows10官方原版系统下载安装制作方法
Windows10官方原版系统下载安装制作方法 去官网下载系统安装程序 点进去 https://www.microsoft.com/zh-cn/software-download/windows10 ...
- 这个 Python 代码自动补全神器搞得我卧槽卧槽的
是时候跟你说说这个能让你撸代码撸得舒服得不要不要的神器了——kite. ! 简单来说,它是一款 IDE 的插件,能做到代码自动补全,可能你会说了,这有什么牛逼的?一般的编辑器不都有这个功能么 ...
- Openshift部署流程介绍
背景 Openshift是一个开源容器云平台,是一个基于主流的容器技术Docker和Kubernetes构建的云平台.Openshift底层以Docker作为容器引擎驱动,以Kubernetes 作为 ...
- POJ 3253 Fence Repair 贪心 优先级队列
Fence Repair Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 77001 Accepted: 25185 De ...