0x00 最大流

最大流量问题涉及通过最大单源单汇流网络找到可行流量。如图所示。

每个边缘都标有容量,即可以携带的最大物品数量。 最大流问题是找出可以从顶点source到顶点sink总的物品数量(最大的那个)。

如上图所示,当0->12->1汇聚的时候不能超出1->3的值121的输出最大为12)。最后可以到达5的最大物品个数就是19+4=23。也就是每条边和每个顶点要满足:

  • 每条边上的流量不超过每条边的给定容量。
  • sourcesink之外,每个顶点的流入流等于流出流。

0x01 残差网络和增广路径

对于解决最大流的问题,我们首先将问题简单化,如图所示

首先可以想到的思路就是每次流过的值最大就好啦。所以,从S出发每次选择可以流出的最大流。

通过这种方式的得到的最后结果是3,显然这是错误的(应该是5)。我们首先将上面的结果中被删除的部分拿出来(没有被使用的流)。

通过该删除部分可以构成残差网络。给定图形GGG和其中的流fff,我们形成一个新的流网络GfG_fGf​,该网络具有相同的顶点集GGG,并且每个GGG边缘具有两个边。对于图GGG的边e=(1,2)e =(1,2)e=(1,2) ,对应流为f(e)f(e)f(e),容量为c(e)c(e)c(e),形成的GfG_fGf​的方向为1->2的边(前向弧)容量为c(e)−f(e)c(e)-f(e)c(e)−f(e),方向为2->1的边(后向弧)的容量为f(e)f(e)f(e)。 残差网络如图所示:

参差网络的思想对于解决最大流问题非常有用。

还有一个需要用到的概念是增广路径。所谓增广路径,是指这条路径上的流可以修改,通过修改,使得整个网络的流值增大。 设fff是一个可行流,PPP是从源点sss到终点(汇点)ttt的一条路,若ppp满足下列条件:

  • 在ppp上的所有前向弧(vi→vj)(v_i→v_j)(vi​→vj​)都是非饱和弧,即0≤fi,j<ci,j0≤f_{i,j}<c_{i,j}0≤fi,j​<ci,j​
  • 在ppp上的所有后向弧(vi←vj)(v_i←v_j)(vi​←vj​)都是非零弧,即0<fi,j≤ci,j0<f_{i,j}≤c_{i,j}0<fi,j​≤ci,j​

则称ppp为(关于可行流fff的)一条可增广路径。

0x02 Ford-Fulkerson算法

首先需要知道最大流最小割定理。如果fff是具有源点sss和汇点ttt的流网络G=(V,E)G = (V, E)G=(V,E)中的一个流,则下列条件是等价的:

  • fff是GGG的一个最大流。
  • 残留网络GfG_fGf​不包含增广路径。
  • 对GGG的某个割(S,T)(S, T)(S,T),有∣f∣=c(S,T)|f| = c(S, T)∣f∣=c(S,T)。

上述定理的证明可以参看这里定理证明,这里就不细说。

Ford-Fulkerson算法是一种迭代方法。开始时,对所有u,v∈Vu, v ∈ Vu,v∈V有f(u,v)=0f(u, v) = 0f(u,v)=0,即初始状态时流的值为000。在每次迭代中,可通过寻找一条增广路径来增加流值。增广路径可以看做是从源点sss到汇点ttt之间的一条路径,沿该路径可以压入更多的流,从而增加流的值。反复进行这一过程,直至增广路径都被找出为止。最大流最小割定理将说明在算法终止时,这一过程可产生出最大流。

from collections import defaultdict class Graph: def __init__(self,graph): self.graph = graph self.ROW = len(graph) def bfs(self, s, t, parent):visited = [False]*self.ROWqueue = [s] visited[s] = Truewhile queue: u = queue.pop(0) for ind, val in enumerate(self.graph[u]): if visited[ind] == False and val > 0 : queue.append(ind) visited[ind] = Trueparent[ind] = u return True if visited[t] else Falsedef FordFulkerson(self, source, sink): parent = [-1]*self.ROW max_flow = 0 while self.bfs(source, sink, parent): #判断增广路径 path_flow, s = float("Inf"), sinkwhile s != source: #计算增广路径的最小流量,通过最小流量计算残差网络path_flow = min(path_flow, self.graph[parent[s]][s]) s = parent[s] max_flow += path_flow v = sink while v != source: #计算残差网络u = parent[v] self.graph[u][v] -= path_flow self.graph[v][u] += path_flow v = parent[v] return max_flow graph = [[0, 16, 13, 0,  0,  0], [0, 0,  10, 12, 0,  0], [0, 4,  0,  0,  14, 0], [0, 0,  9,  0,  0, 20], [0, 0,  0,  7,  0,  4], [0, 0,  0,  0,  0,  0]] g = Graph(graph)
source, sink = 0, 5
print("The maximum possible flow is %d " % g.FordFulkerson(source, sink))

该算法的时间复杂度是O(VE2)O(VE^2)O(VE2)。

0x03 Dinic算法

Ford-Fulkerson算法对于稀疏图来说还不错,但是当边的数目过多的时候我们就需要更好的Dinic算法,该算法的时间复杂度是O(EV2)O(EV^2)O(EV2)。

首先使用给定的图初始化残差网络(和之前做法一样),如图所示。

第一次迭代:我们使用BFS为所有节点分配级别,同事需要检查是否有增广路径(残差网络中是否存在s→ts→ts→t路径)。

现在,我们使用级别来发送流(意味着每个流路径的级别都应为0123)。与Ford-Fulkerson算法相比,我们一次发送三个流。

  • 路径s–1–3–ts – 1 – 3 – ts–1–3–t上有4个流量单位。
  • 路径s–1–4–ts – 1 – 4 – ts–1–4–t上有6个流量单位。
  • 路径s–2–4–ts – 2 – 4 – ts–2–4–t上有4个流量单位。

总流量=4 + 6 + 4 = 14。一轮迭代后,残差图变为下图。

第二次迭代:我们使用bfs遍历上述修改后的残差图,为所有节点分配新级别。

现在,我们使用级别来发送流(意味着每个流路径的级别都应为01234)。 这次我们只能发送一个流。

  • 路径s–2–4–3–ts – 2 – 4 – 3 – ts–2–4–3–t上有5个流量单位

总流量=总流量+ 5 = 19。二轮迭代后,残差图变为下图。

第二次迭代:此时已经没有增广路径了,所以结束。

#include <iostream>
#include <cstring>
using namespace std;const int N = 1e5 + 10, M = N * 2;
struct Edge {int to, next, w;
} edge[M];int cur[N], head[N], idx = -1; //cur用于当前弧优化
int n, m, s, t;void add(int u, int v, int w)
{edge[++idx].to = v;edge[idx].next = head[u];edge[idx].w = w;head[u] = idx;
}int q[N], level[N];
bool bfs(int u, int v) //判断是否存在增广路径,构建路径级别
{int hh = 0, tt = 0;q[0] = u;memset(level, -1, sizeof level);level[u] = 0;while (hh <= tt){int tmp = q[hh++];for (int i = head[tmp]; ~i; i = edge[i].next){int son = edge[i].to;if (edge[i].w <= 0 || ~level[son]) continue;level[son] = level[tmp] + 1;q[++tt] = son;}}return ~level[v];
}int dfs(int u, int v, int flow) //寻找增广路径
{if (u == v) return flow;int res = 0;//注意使用引用,这样i变化的同时也能改变cur[u]的值,达到记录当前弧的目的for (int& i = cur[u]; ~i; i = edge[i].next){int son = edge[i].to;if (edge[i].w <= 0 || level[son] != level[u] + 1) continue;if (res = dfs(son, v, min(edge[i].w, flow))){edge[i].w -= res; edge[i^1].w += res;return res;}}return res;
}int dinic(int u, int v)
{int res = 0;while (bfs(u, v)){for (int i = 0; i <= n; i++) cur[i] = head[i];res += dfs(u, v, 0x3f3f3f3f);}return res;
}int main()
{cin >> n >> m >> s >> t;memset(head, -1, sizeof head);for (int i = 0; i < m; ++i){int a, b, w;cin >> a >> b >> w;add(a, b, w), add(b, a, 0);}cout << dinic(s, t);return 0;
}

reference:

https://www.geeksforgeeks.org/max-flow-problem-introduction/

https://www.geeksforgeeks.org/ford-fulkerson-algorithm-for-maximum-flow-problem/

https://www.geeksforgeeks.org/dinics-algorithm-maximum-flow/

https://www.cnblogs.com/gaochundong/p/ford_fulkerson_maximum_flow_algorithm.html

最大流问题(超详细!!!)相关推荐

  1. 二分图(超详细!!!)

    0x00 二分图 二分图是一个图,它的顶点可以分为两个独立的集合u和v,这样每一条边(u,v)要么从u到v连接一个顶点,要么从v到u连接一个顶点.换句话说,对于每一条边(u,v),要么u属于u,要么v ...

  2. k8s核心组件详细介绍教程(配超详细实例演示)

    本文实验环境基于上篇文章手把手从零开始搭建k8s集群超详细教程 本文根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps学习总结而来 k8s核心组件介绍 1 ...

  3. 手把手从零开始搭建k8s集群超详细教程

    本教程根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps同步所做笔记教程 k8s集群搭建超详细教程 1. 基本环境搭建 1. 创建私有网络 2. 创建服务 ...

  4. 归并排序(代码注释超详细)

    归并排序: (复制粘贴百度百科没什么意思),简单来说,就是对数组进行分组,然后分组进行排序,排序完最后再整合起来排序! 我看了很多博客,都是写的8个数据呀什么的(2^4,分组方便),我就想着,要是10 ...

  5. 超详细的Java面试题总结(四 )之JavaWeb基础知识总结

    系列文章请查看: 超详细的Java面试题总结(一)之Java基础知识篇 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java ...

  6. 400 多行代码!超详细 Rasa 中文聊天机器人开发指南 | 原力计划

    作者 | 无名之辈FTER 责编 | 夕颜 出品 | 程序人生(ID:coder_life) 本文翻译自Rasa官方文档,并融合了自己的理解和项目实战,同时对文档中涉及到的技术点进行了一定程度的扩展, ...

  7. 一份超详细的数据科学路线图!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 作者:魔王.陈萍,来源:机器之心 从头开始学习数据科学的免费资源. 如何 ...

  8. OSSIM系统的安装教程(超详细)

    OSSIM系统的安装教程(超详细) 一.创建虚拟机 二.安装ossim系统 三.web登录 一.创建虚拟机 首先在网上下载OSSIM系统的镜像,记住自己保存的位置. 打开vm,新建虚拟机. 选择自定义 ...

  9. 超详细的 Redis Cluster 官方集群搭建指南,适用于 redis 5.x, 6.x

    今天从 0 开始搭建 Redis Cluster 官方集群,解决搭建过程中遇到的问题,超详细. 旧版本使用 redis-trib.rb ruby 脚本安装集群,5.0版本redis-cli 已经自带 ...

  10. Python的零基础超详细讲解(第十三天)-Python的类与对象

    基础篇往期文章如下: Python的零基础超详细讲解(第一天)-Python简介以及下载 Python的零基础超详细讲解(第二天)-Python的基础语法1 Python的零基础超详细讲解(第三天)- ...

最新文章

  1. 调查报告:工人们并不担心将来会被AI取代
  2. centos iptables 防火墙配置
  3. android 活动说明,Android – 如何发送GCM推送通知以及要加载哪些活动的说明?
  4. 计算机的好处英语,跪求一篇英语作文 题目:论计算机的优缺点
  5. 《C语言开发从入门到精通》一2.4 技术解惑
  6. [转]JSon数据解析的四种方式
  7. rocketmq怎么保证数据不会重复_阿里架构师亲授:Kafka和RocketMQ的消息复制实现的差异点在哪?...
  8. ThinkingInJava 学习 之 0000002 操作符
  9. 简化版XP按装IIS5.1实录
  10. 基于MSBuild的xnb资源预生成机制
  11. iptables 学习笔记 一 要领入门
  12. 2021美赛MCM选题
  13. linux mpeg4ip 编译,利用Linux实现MPEG4流媒体技术
  14. 禅宗公案 艾舍尔的画与哥德尔定理——哥德尔逻辑与哲学之2
  15. 【九阳真经口诀】他强由他强,清风拂山岗;他横由他横,明月照大江。他自狠来他自恶,我自一口真气足。
  16. 《今日简史》--意义:人生不是虚构的故事
  17. 关于《十天学会AVR单片机》的教程头文件AVR_PQ1A.h
  18. 阿龙的学习笔记---CMake指定C++版本
  19. 理论篇-地图学与GIS制图的基础理论(二)
  20. java/php/net/python养花助手平台设计

热门文章

  1. 游戏开发:Html5 虚拟摇杆控制人物移动
  2. object c中的多态
  3. MATLAB规划和LINGO规划,[数学建模]线性规划与matlab,lingo解法
  4. itextpdf 简介
  5. 配置虚拟机NAT模式连通外网并使用Xshell登陆
  6. 没有基础怎么学习PLC编程?
  7. 蓝绿色——三色配色篇
  8. bigemap 功能介绍
  9. oracle 存储过程 实例 循环 给查询赋值 游标取值
  10. I18N和L10N测试工具