(菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解
关于网络流:
1.定义
个人理解网络流的意思便是由一条条水管以及一个源点S一个汇点T和一些节点组成的一张图,现在要从S点流水到T点,问怎么流才能让流到T的流量最大。边权表示的是这条水管的最大流量,假设一条水管的边权是4,那么如果往这个水管里流5那么自然就会炸掉。

关于网络流一些文字上的概念,和一张图,这张图的最大流显然是3。
增广路:从s到t的一条简单路径,且每一条边的容量都大于0。
流(flow):每条边对应其流量的集合。
可行流:从s到t的一个流,且每条边的流量不超过其容量限制。
最大流:流量最大的可行流。
那么怎么找最大流呢,不断找增广路,直到找不到为止?
这个算法很明显是错误的,用刚刚那个图就可以说明。盲目地找一条增广路,显然是不够“聪明”的。 但我们似乎没办法让它变得更“聪明”。那我们不妨给他一个”反悔“的就会。
假设有有一条路径从u->v那么我们就建一条v->u的有向边,称之为反向边,他的边权是0。为什么是0?因为刚开始还没水流从u->v流过,所以没有水可以反悔,假若从u->v流过x的水,那么v->u的边权就是x,因为他可以反悔x的水。
这张图就说明了他的反向边地优越性两条流向撞还是两条流!在做的各位可能跟我刚开始一样起了疑问,两条流撞在一起万一水管小不会爆掉吗?嗯,其实他指的是流量,一道水流过这条水管后,他还会流到别的水管,难道流过一次水管就不能流了吗?我们考虑的是流量,这就要追究的问题,他问的不是一堆水从S点流向其他管子里,问流到T的最多是多少,而是流量最多是多少。
那么如果以这个想法,之前那么想法显然就是对的了,这种算法叫做Ford-Fulkerson算法,但是如果单纯的这么做显然时间复杂度不允许,算一算他的复杂度是O(maxflow),他的时间复杂度由边权决定。但是还是得学。我们把它简称为FF算法。
FF算法的流程:
1.建出带反向边的网络。
2.不断Dfs找增广路,然后沿着增广路流。
3.直到找不到增广路。
完美
代码实现:
1.首先初始化:
const int oo=0x7fffffff; //可爱的无限大
struct e{ //边
int cap,to,from; //cap是边权,to是到达的点,from是从哪里来
int next,rev; //rev是反向边的下标。
}edge[200005];
int head[200005];
bool vis[200005]; //记录每个点访问了没有。
int n,m,s,f; //n是点个数,m是边个数,s是起点,f是终点
int cur,ans; //ans自然就是答案
2.用链式前向星存边:
void Scanf(int x,int y,int z){ //x是来自何方,y是到何方,z是权值
cur++; //大写的Scanf格外诱人
edge[cur].cap=z;
edge[cur].to=y;
edge[cur].from=x;
edge[cur].next=head[x];
head[x]=cur;
edge[cur].rev=cur+1; //指向他的反向边
cur++;
edge[cur].cap=0; //反向边
edge[cur].to=x;
edge[cur].from=y;
edge[cur].next=head[y];
head[y]=cur;
edge[cur].rev=cur-1; //指向他的反向边
}
3.接着是核心部分:
int Search_flow(int node,int flow){//找增广路用的DFS
if(node==f||flow==0)return flow;//如果水都流没了或者当前搜索到的点已经是终点那么自然就返回流量。
vis[node]=true; //标记该点走过
int tre=0; //记录流量
for(int p=head[node];p;p=edge[p].next){
if(!vis[edge[p].to]&&edge[p].cap){//如果没访问过而且边权大于0
int fuck=Search_flow(edge[p].to,min(flow,edge[p].cap));//fuck不要介意,就接着找增广路。
flow-=fuck; //流量减掉已经流掉的
tre+=fuck; //加上流过的水量
edge[p].cap-=fuck; //减掉流过的
edge[edge[p].rev].cap+=fuck;//给他反悔的机会反向边加上流掉的量
}
}
vis[node]=false;//将此点还原
return tre; //返回流量
}
int Search_ans(){
int flow=0;
while(1){
for(int i=1;i<=n;i++)vis[i]=false; //清0不重要
int new_flow=Search_flow(s,oo); //一直找增广路
if(new_flow>0)flow+=new_flow; //要是找得到那么最大流加上这个数
else break; //否则说明没路可走退出
}
return flow;
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int oo=0x7fffffff;
struct e{
int cap,to,from;
int next,rev;
}edge[200005];
int head[200005];
bool vis[200005];
int n,m,s,f;
int cur,ans;
void Scanf(int x,int y,int z){
cur++;
edge[cur].cap=z;
edge[cur].to=y;
edge[cur].from=x;
edge[cur].next=head[x];
head[x]=cur;
edge[cur].rev=cur+1;
cur++;
edge[cur].cap=0;
edge[cur].to=x;
edge[cur].from=y;
edge[cur].next=head[y];
head[y]=cur;
edge[cur].rev=cur-1;
}
int Search_flow(int node,int flow){
if(node==f||flow==0)return flow;
vis[node]=true;
int tre=0;
for(int p=head[node];p;p=edge[p].next){
if(!vis[edge[p].to]&&edge[p].cap){
int fuck=Search_flow(edge[p].to,min(flow,edge[p].cap));
flow-=fuck;
tre+=fuck;
edge[p].cap-=fuck;
edge[edge[p].rev].cap+=fuck;
}
}
vis[node]=false;
return tre;
}
int Search_ans(){
int flow=0;
while(1){
for(int i=1;i<=n;i++)vis[i]=false;
int new_flow=Search_flow(s,oo);
if(new_flow>0)flow+=new_flow;
else break;
}
return flow;
}
int main(){
cin>>n>>m>>s>>f;
for(int i=1;i<=m;i++){
int ui,vi,si;
cin>>ui>>vi>>si;
Scanf(ui,vi,si);
}
int ans=Search_ans();
cout<<ans<<endl;
return 0;
}
然后呢我们来讲讲更优的算法名字叫做Dinic算法。开一个数组level表示从源点到达当前点的最小步数。
Dinic的核心思想:每次Dfs都只找经过边数最少的增广路。 具体实现方法是:在Dfs前,先用容量>0的边进行一次Bfs,定出每个点的level。在之后的Dfs中,只允许使用level[v]==level[u]+1的边u->v。 这样Dfs时,我们相当于在一个DAG上增广,所有增广路长度都是level[t]的。增广完所有长度为level[t]的增广路后停止。这样就会节省时间。
Tip:Dinic的时间复杂度是O(nm²)。某种不成文的规定,凡是正解是最大流的题目,不允许卡Dinic.QWQ
Dinic算法流程:
用有容量的边进行Bfs,对网络中的点分层。
只使用符合分层情况的边,进行一次Dfs(多路增广),增广完所有长度为level[t]的增广路。
重复上面两个过程,直到s通过Bfs无法到达t。
Dinic的当前弧优化:记录上次Dfs到这个点时,扫到哪一条边。下次再到这个点时,直接从该边开始,避免对一条边进行无用的检查。
代码核心:
int BFS(){ //BFS找到level
for(int i=1;i<=n;i++){ //初始化
level[i]=0;
nhead[i]=head[i];
}
queue<int> q; //BFS的队列首先从起点s开始找
q.push(s);
level[s]=1; //自己到自己设置为1
while(!q.empty()){
int x=q.front();
q.pop();
for(int p=head[x];p;p=e[p].next)
if(e[p].cap&&!level[e[p].to]){ //如果当前边的边权>0并且没有标记过就标记
q.push(e[p].to);
level[e[p].to]=level[x]+1;
}
}
return level[t]; //返回终点的值如果
}
int DFS(int root,int flow){
if(root==t||!flow)return flow; //与FF中同理同理
int tre=0;
for(int p=nhead[root];p;p=e[p].next) //当前弧优化
if(e[p].cap&&level[root]+1==level[e[p].to]){
int new_flow=DFS(e[p].to,min(flow,e[p].cap));
flow-=new_flow; //不解释,与FF同理
tre+=new_flow;
e[p].cap-=new_flow;
e[e[p].rev].cap+=new_flow;
if(!flow)break; //如果已经流完了那么就跳出循环
nhead[root]=p; //当前弧优化
}
return tre;
}
int Dinci(){
int max_flow=0;
while(BFS())max_flow+=DFS(s,oo); //如果返回终点的值是0说明已经无法到达终点了,否则找增广路
return max_flow; //因此返回最大流输出
}
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
const int oo=0x7fffffff;
struct edge{
int from,to,cap;
int rev,next;
}e[200005];
int head[200005];
int nhead[200005];
int cur;
int level[200005];
void Scanf(int x,int y,int z){
cur++;
e[cur].from=x;
e[cur].to=y;
e[cur].cap=z;
e[cur].next=head[x];
head[x]=cur;
e[cur].rev=cur+1;
cur++;
e[cur].from=y;
e[cur].to=x;
e[cur].cap=0;
e[cur].next=head[y];
head[y]=cur;
e[cur].rev=cur-1;
}
int BFS(){
for(int i=1;i<=n;i++){
level[i]=0;
nhead[i]=head[i];
}
queue<int> q;
q.push(s);
level[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int p=head[x];p;p=e[p].next)
if(e[p].cap&&!level[e[p].to]){
q.push(e[p].to);
level[e[p].to]=level[x]+1;
}
}
return level[t];
}
int DFS(int root,int flow){
if(root==t||!flow)return flow;
int tre=0;
for(int p=nhead[root];p;p=e[p].next)
if(e[p].cap&&level[root]+1==level[e[p].to]){
int new_flow=DFS(e[p].to,min(flow,e[p].cap));
flow-=new_flow;
tre+=new_flow;
e[p].cap-=new_flow;
e[e[p].rev].cap+=new_flow;
if(!flow)break;
nhead[root]=p;
}
return tre;
}
int Dinci(){
int max_flow=0;
while(BFS())max_flow+=DFS(s,oo);
return max_flow;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int ui,vi,wi;
scanf("%d%d%d",&ui,&vi,&wi);
Scanf(ui,vi,wi);
}
printf("%d",Dinci());
return 0;
}
还有一个问题,没错就是最小割,你只要记住最大流=最小割,也就是叫你求最小割时你就求最大流就行了,网络流这个东西,最主要的是建图,模板的话每天打一两次就好了,提醒一下建图可以运用:拆点,超级源点,超级汇点等思想。
Tip:超级源点,超级汇点指在题目中没有确切给出源点和汇点,然后通过自己的构思,创造出源点和汇点。
那么简单讲讲最小割,就是说在一张最大流的图中求割掉一些边使得源点到不了汇点(或者说是最大流为0)求出割掉的边的边权和最小。
这就是最小割,最小割也有变形,比如说题目说要割点不割边,像这种情况就可以利用拆点,也就是将一个点拆分成两个点,割掉两点中的线好比割掉了这个点,然后确定这条边的边权,再跑一遍最大流即可。
不管是最大流还是最小割最关键的东西就是建图。
转载的话请加上原文网址,哦
谢谢您的观看,记得点个赞再走哦,有什么写得不好的多多指教。
(菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解的更多相关文章
- 小学生都能看懂的FFT!!!
小学生都能看懂的FFT!!! 前言 在创新实践重心偷偷看了一天FFT资料后,我终于看懂了一点.为了给大家提供一份简单易懂的学习资料,同时也方便自己以后复习,我决定动手写这份学习笔记. 食用指南: 本篇 ...
- 机器学习敲门砖:任何人都能看懂的TensorFlow介绍
机器学习敲门砖:任何人都能看懂的TensorFlow介绍 http://www.jiqizhixin.com/article/1440
- 只要听说过电脑的人都能看懂的网上pdf全书获取项目
作者:周奇 最近我要获取<概统>的教材自学防挂科(线代已死),于是我看到 htt链ps:/链/max链.book接118接.com接/html/2018/0407/160495927.sh ...
- 55张图吃透Nacos,妹子都能看懂!
大家好,我是不才陈某~ 这是<Spring Cloud 进阶>第1篇文章,往期文章如下: 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强? openFeign夺命连环9问,这谁受得 ...
- 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)
一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...
- 网络编程之TCP/IP各层详解
网络编程之TCP/IP各层详解 我们将应用层,表示层,会话层并作应用层,从TCP/IP五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议,就理解了整个物联网通信的原理. 首先,用户感知到的只 ...
- WiFi无线网络参数 802.11a/b/g/n 详解
转载自:WiFi无线网络参数 802.11a/b/g/n 详解 如转载侵犯您的版权,请联系:2378264731@qq.com 802.11a/b/g/n,其实指的是无线网络协议,细分为802.11a ...
- linux网络编程之shutdown() 与 close()函数详解
linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...
- Linux网络配置:Nat和桥接模式详解
Linux网络配置:Nat和桥接模式详解 一.我们首先说一下VMware的几个虚拟设备: Centos虚拟网络编辑器中的虚拟交换机: VMnet0:用于虚拟桥接网络下的虚拟交换机: VMnet1:用于 ...
随机推荐
- 又陷入知识盲区了,面试被问SpringBoot集成dubbo,我当时就懵了
前言 前两天在和粉丝聊天的时候,粉丝跟我说之前在面试的时候被问到SpringBoot这一块的知识被问的有点懵,和我问了不少这方面的东西.事后我想了想不如把这些东西分享出来吧,让更多的人看到,这样不管是 ...
- 详讲FL Studio通道设置菜单
我们在FL Studio"通道设置按钮"上右击鼠标就会弹出一个设置菜单,它包含了通道操作的各种常用命令.下文小编将会为大家详细讲解这些命令的具体作用,一起来学习吧! 1.首先,我们 ...
- Redis多线程原理详解
本篇文章为你解答一下问题: 0:redis单线程的实现流程是怎样的? 1:redis哪些地方用到了多线程,哪些地方是单线程? 2:redis多线程是怎么实现的? 3:redis多线程是怎么做到无锁的? ...
- cocoslua3.17 android机器上播放音效不全
开发过程中遇到一个问题,一个8秒的音效,在android机器上播放不完就结束了:网上说是由于android播放音效的内存限制的:原因知道了,那怎么解决呢? 通过各种搜索查找发现还是解决不了问题,然后自 ...
- Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
点波关注不迷路,一键三连好运连连! 先贴上几个案例,水平高超的同学可以挑战一下: 从员工集合中筛选出salary大于8000的员工,并放置到新的集合里. 统计员工的最高薪资.平均薪资.薪资之和. 将员 ...
- json套娃其实是这样套的!
- JavaScript的执行上下文,真没你想的那么难
作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/2436173500265335 前言 在正文开始前,先来看 ...
- DjangoForm表单组件
Form组件的介绍: 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验 ...
- PyQt(Python+Qt)学习随笔:QDockWidget停靠部件floating和features属性
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 1.floating属性 floating属性表示QDockWidge ...
- 第15.36节 PyQt(Python+Qt)入门学习:containers容器类部件QFrame框架部件介绍
一.概述 容器部件就是可以在部件内放置其他部件的部件,在Qt Designer中可以使用的容器部件有如下: 容器中的Frame为一个矩形的框架对象,对应类QFrame,QFrame类是PyQt中带框架 ...