引言
在最大流(一)中我们讨论了关于EK算法的原理与代码实现,此文将讨论与EK算法同级别复杂度(O(N^2M))的算法——Dinic算法。
Dinic算法用到的思想是图的分层结构,通过BFS将每一个节点标出层次后DFS得到当前增广路。然后继续在残留网络中进行BFS分层,当汇点不在层次网络时(没有连通弧了),算法结束。


Dinic算法结构
0.初始化(边表)
1.BFS分层——汇点不在层次网络中跳出
2.DFS寻找增广路
3.输出最大流


关于初始化
在最大流(一)中,在MLE的情况下我们舍弃了邻接矩阵的存储方式转而使用边表存储每一条弧的信息。存储方式与(一)中相同。
即定义结构体:

struct qi
{int st, en, num;//首、尾、流量限制
}flow[maxm];//边表 

采用读入优化读取数据。

int read()//读入优化
{char a;int input = 0;a = getchar();while(a < '0' || a > '9')a = getchar();while(a >= '0' && a <= '9'){input = input*10+a-'0';a = getchar();}return input;
}

将每一条弧与序号一一对应

for(i = 0; i != m; ++i)
{low[i].st = read(), flow[i].en = read(), flow[i].num = read();re[flow[i].st][++num[flow[i].st]] = i;//编号与弧的一一映射 re[flow[i].en][++num[flow[i].en]] = m+i;//定义反向弧的编号与该弧的关系
}

初始化反向弧信息:

for(i = m; i != m+m; ++i)//反向弧流量限制初始为0
{flow[i].st = flow[i-m].en;flow[i].en = flow[i-m].st;flow[i].num = 0;
}

如此初始化完成。


BFS分层:
概念:所谓分层就是将图的每一个节点按照某一个标准分类。在Dinic算法中,标准是每一个节点到源点的最短路径(经过几条弧),由此得到每一个节点的层级(源点层次为0,以此类推)。
目的:限制每一次寻找增广路的时候使其在寻找增广路时不会出现浪费。若i -> j,需满足lev[j] = lev[i]+1。
实现方法广度优先搜索。记录每一个与之相邻的节点的等级为cur+1,每个节点每次广搜中只遍历一次

由此可得到两种结果
1.汇点的层次是N(N > 0);
2.汇点没有层次(N == 0);
结果为2时我们结束算法,因为在残余网络中已不存在增广路了。
如果结果是1我们继续进行操作——DFS寻找增广路(如下)。


关于DFS寻找增广路:

注意深搜在if中。

int Dfs(int curr, int min_flow)//寻找增广路
{int i, j, a = 0;if(curr == en)  return min_flow;//遍历到汇点返回for(i = start[curr]; i != num[curr]+1; ++i)//每一个与之相连的节点{++start[curr];//当前弧优化j = flow[re[curr][i]].en;//当前节点的下一个节点if(lev[j] == lev[curr]+1 && (a = Dfs(j, min(min_flow, flow[re[curr][i]].num))))//here{flow[re[curr][i]].num -= a;flow[re[curr][i]+m].num += a;if(min_flow == 0)   break;//优化,当找不到增广路时直接跳出return a;}}   return 0;//遍历不到退出
}

解释一下if的条件。

    if(lev[j] == lev[curr]+1 && (a = Dfs(j, min(min_flow, flow[re[curr][i]].num))))

首先根据算法应该满足级数比当前级数大1;
其次进行深搜,实际上循环中的代码只是用来更新流量的,通过回溯更新当前增广路的流量限制。真正寻找增广路的部分在于后面括号中的代码

也就是

(a = Dfs(j, min(min_flow, flow[re[curr][i]].num))

当a != 0 时,会返回true(!0),得到0时会返回false,当返回false的时候说明当前路径不是增广路,故直接跳出。(函数最后的return 0)


关于当前弧优化:
因为每一个节点可能有多个节点与之相连,故DFS遍历的时候可能会再次访问到。
例子:
节点P**已经被遍历,而且已经遍历到与之相连的第二个节点。被再次遍历到,说明要继续遍历与之相邻的节点,因为前两个节点(在本个例子中)已经在之前已经遍历过,所以应该直接从第三个开始遍历。故用一个start[i]记录已经遍历到的相邻节点个数(也是第几个),使得在将来访问时不重复深搜已遍历节点。


Dinic:

int Dinic(int st_pos, int end_pos)
{int i, minn, max_flow = 0;while(Bfs(st, en)){memset(start, 0, sizeof start);//每次深搜将上次已遍历节点数清零while(minn = Dfs(st, INF))  max_flow += minn;}return max_flow;
}

这个就是之前整个算法的结构的代码形式


完整代码:

/*
Algorithm: Dinic
Author: kongse_qi
date: 2017/04/09
*/#include <bits/stdc++.h>
#define INF 0x3f3f3f
#define maxm 200005
#define maxn 10005
using namespace std;int n, m, st, en, num[maxn], re[maxn][maxn/10], lev[maxn], minn, start[maxn];
struct qi{int st, en, num;}flow[maxm];//边表
bool wh[maxn];int read()//读入优化
{char a;int input = 0;a = getchar();while(a < '0' || a > '9')a = getchar();while(a >= '0' && a <= '9'){input = input*10+a-'0';a = getchar();}return input;
}void Init()//初始化
{int i;memset(num, -1, sizeof num);n = read(), m = read(), st = read(), en = read();for(i = 0; i != m; ++i){flow[i].st = read(), flow[i].en = read(), flow[i].num = read();re[flow[i].st][++num[flow[i].st]] = i;//编号与弧的一一映射 re[flow[i].en][++num[flow[i].en]] = m+i;//定义反向弧的编号与该弧的关系 }for(i = m; i != m+m; ++i)//反向弧流量限制初始为0 {flow[i].st = flow[i-m].en;flow[i].en = flow[i-m].st;flow[i].num = 0;}return ;
}bool Bfs(int st, int en)//BFS将图分层
{int i, j, ne, st_pos = -1, end_pos = 0, curr_pos, q[maxn], tot = 1;bool wh_con = 0;lev[st] = 0;//初始源点层数为0 memset(wh, 0, sizeof wh);memset(lev, 0, sizeof lev);wh[st] = 1;q[0] = st; while(st_pos != end_pos){curr_pos = q[++st_pos];for(i = 0; i != num[curr_pos]+1; ++i){j = re[curr_pos][i];//当前弧 ne = flow[j].en;//当前弧的终点 if(!wh[flow[j].en] && flow[j].num > 0)//流量限制>0 && 此次未遍历 {if(ne == en)    wh_con = 1;//源点在残余网络之中 wh[ne] = 1;q[++end_pos] = ne;lev[ne] = lev[curr_pos]+1; ++tot;}if(tot == n)    return 1;//优化1:整个网络完成遍历后直接退出 }}return wh_con;
}int Dfs(int curr, int min_flow)//寻找堵塞流
{int i, j, a = 0;if(curr == en || min_flow == 0) return min_flow;for(i = start[curr]; i != num[curr]+1; ++i){++start[curr];j = flow[re[curr][i]].en;if(lev[j] == lev[curr]+1 && (a = Dfs(j, min(min_flow, flow[re[curr][i]].num)))){flow[re[curr][i]].num -= a;flow[re[curr][i]+m].num += a;if(min_flow == 0)   break;return a;}}   return 0;
}int Dinic(int st_pos, int end_pos)
{int i, minn, max_flow = 0;while(Bfs(st, en)){memset(start, 0, sizeof start);while(minn = Dfs(st, INF))  max_flow += minn;}return max_flow;
}int main()
{//freopen("test.in", "r", stdin);//freopen("test.out", "w", stdout);Init();printf("%d", Dinic(st, en));//fclose(stdin);//fclose(stdout);
}

至此便完成了Dinic算法。


实测效率
还是在luogu的P3376 【模板】网络最大流中测评。

结果:耗时/内存 429ms , 56949kb
相比于EK算法的:耗时/内存 392ms , 56988kb 似乎要慢上一些,实际上关于网络流算法的时间复杂度是玄学,只能得到上限(最坏情况)无法得到每次实际的复杂度。因为是BFS+DFS,次数,节点连通情况我们都无法计算,故时间复杂度意义不大,实测结果与具体数据有关。
通常不会超时,因为面对极坑的数据(例如真的到了N^2M的情况),奈何出题人再优化他的标程也不可能按时跑出来(100000* 100000* 10000,你行你来…),会改数据的…
所以在使用这种算法的时候还是别考虑在这上面优化了千万不要忘记读入优化,比你优化别的半天强多了)。

至此Dinic算法的分析便结束了。
箜瑟_qi 2017.04.09 20:42

转载于:https://www.cnblogs.com/kongse-qi/p/6798886.html

最大流算法之Dinic相关推荐

  1. HDU3549(最大流算法的Dinic算法)

    题目:Flow Problem #include <iostream> #include <stdio.h> #include <string.h> using n ...

  2. 最大流算法(Edmons-Karp + Dinic 比较) + Ford-Fulkson 简要证明

    Ford-Fulkson用EK实现:483ms #include <cstdio> #include <cstring> #define min(x,y) (x>y?y: ...

  3. 最大流EK和Dinic算法

    最大流EK和Dinic算法 EK算法 最朴素的求最大流的算法. 做法:不停的寻找增广路,直到找不到为止 代码如下: @Frosero #include <cstdio> #include ...

  4. 最大流算法模板:EK和dinic算法

    最大流算法模板:EK和dinic算法 一.EK算法模板 #include<iostream> #include<queue> using namespace std; cons ...

  5. 网络流之最大流算法——EK算法(通俗讲解)

    先放道模板题来说明网络流: Power Network A power network consists of nodes (power stations, consumers and dispatc ...

  6. 一种更高效的费用流算法——zkw费用流

    orz原创者zkw%%% 送上zkw神犇的blog原址:传送门 费用流建立在网络最大流的基础上,一张图中最大流有且仅有一个,但是最大流条数往往不止一条,这时候对于我们来说,可能要找出这些最大流中最小( ...

  7. 最大流算法之三:ISAP

    最大流算法之三:ISAP <转> (2009-08-14 19:24:27) 转载▼ 标签: it 分类: 理论 通常的 SAP 类算法在寻找增广路时总要先进行 BFS,BFS 的最坏情况 ...

  8. 最大流算法-ISAP

    引入 最大流算法分为两类,一种是增广路算法,一种是预留推进算法.增广路算法包括时间复杂度\(O(nm^2)\)的EK算法,上界为\(O(n^2m)\)的Dinic算法,以及一些其他的算法.EK算法直接 ...

  9. 接口限流算法:漏桶算法令牌桶算法

    工作中对外提供的API 接口设计都要考虑限流,如果不考虑限流,会成系统的连锁反应,轻者响应缓慢,重者系统宕机,整个业务线崩溃,如何应对这种情况呢,我们可以对请求进行引流或者直接拒绝等操作,保持系统的可 ...

  10. 搞懂限流算法这一篇就够了

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 TL;DR(too long don't read) 限流算法:计 ...

最新文章

  1. 【OpenCV】将图像数据由YUV格式转换成JPG格式直接使用,而不保存成文件
  2. 结合EM快速解决复杂的配置问题
  3. ecplice中class.forname一直报错_Python怎么把文件内容读取出来,怎么把内容写入文件中
  4. Winform中设置BackgroundWorker在取消时关闭后台进程不生效-没有跳出循环
  5. QT的QEnableSharedFromThis类的使用
  6. 让oracle做定时任务【转】
  7. python作中国地图背景气泡图_exce表格中怎么制作中国地图背景数据气泡图
  8. 万字长文|深度剖析Service Mesh服务网格新生代Istio
  9. Java统计做题正确率_ResNet:训练期间的准确率为100%,但使用相同数据的预测准确率为33%...
  10. 【华为云技术分享】一文带你了解Web前端发展历程
  11. word2vec的理解
  12. 【分享】Adobe Flash Player各版本安装包官方直接下载地址
  13. 人工智能可以产生自主意识吗?
  14. artDialog | 经典的网页对话框组件
  15. 儿童专注力训练之数图形
  16. 养肾=养命!这5个养肾方法,程序员都保存好了!
  17. vs 的 tfs 账号更改
  18. 欧美html游戏安卓,HTML5 Games - Rated M or for 18+ only
  19. 上楼梯(df和dp解法)
  20. BIOS设置|win10最快启动的方式

热门文章

  1. 微商模式的出路在哪里?
  2. Qt创建Qt Designer自定义控件及使用
  3. 一个放在口袋里的项目,将社交裂变做到了极致
  4. 安卓程序员需要什么证书
  5. hdmi 计算机 接口类型,四大常用视频接口对比,你的电脑是哪种接口?
  6. 使用Java+MapReduce实现热词发现
  7. IDEA安装Yapi插件
  8. 数据结构合并两个有序链表
  9. PR转场预设 鼠标拖拽视频画面滑动转场特效PR预设
  10. 企业微信的聊天记录保存在了哪里?