网络流学习笔记

这篇具有很好参考价值的文章主要介绍了网络流学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

网络流

何为网络流

       想要弄清楚网络流,首先要知道网络的概念,通常在运筹学中,网络是指一个有向图$G\ =\ (V,E)$ 。其每条边$(u,v)\in E$都有一个权值$c(u,v)$,称为这条边的流量(Capacity),还有两个特殊的点,一个是源点(Source),一个是汇点(Sink)在图论中,网络流(英语:Network flow)在作为网络的有向图中分配流,使一条边的流量不会超过它的容量。

网络流的性质

1.容量限制

$\ \ \ \ $ 对于有向图\(G\)中的每一条边上所流经的流量不得超过其容量,即 \(f(u,v) \ ≤ \ c(u,v)\)

2.斜对称性

$\ \ \ \ $ 每条边的流量与相反边的流量之和为0,即 \(f(u,v) \ = \ -f(u,v)\)

3.流守恒性

$\ \ \ \ $ 从源点流出的流量等于汇点流入的流量,即 \(\forall x\in V-\{s,t\},\sum_{(u,x)\in E}f(u,x)=\sum_{(x,v)\in E}f(x,v)\)

$\ \ \ \ $ 现在想象下面一个情景,工厂里有一个运输液体的传输管道,工厂在每条管道的都设置了防止倒流的装置,因为压强问题,所以每条管道在单位时间内的容量有限,这就是一个网络流模型了。

事实上,网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。

网络流的常见问题

$\ \ \ \ $ 网络流问题中常见的有以下三种:最大流,最小割,费用流。

最大流

$\ \ \ \ $ 顾名思义,就是求从源点到汇点的最大流量。下面介绍几种常见的方法,其中Dinic算法几乎能完成所有与最大流相关的问题,因为他的复杂度比较优秀。

Ford-Fulkerson增广

$\ \ \ \ $ Ford-Fulkerson增广是计算最大流算法的一类统称,核心思想是贪心,通过寻找增广路来更新并求解最大流。其中,寻找增广路其实就是寻找从源点到汇点的剩余容量非空的路径,对于一条增广路,我们给每一条\((u,v)\)都加上等量的流量,以令整个网络的流量增加,这一过程被称为增广(Augment)。,但是,在执行过程中,会发现这样的思路有可能导致增广了一些原来不应存在于最大流的路径,怎么办?

$\ \ \ \ $ 我们可以利用网络流的斜对称性,在增广时进行退流操作,如果增广出了\((u,v)\)之间的双向流量实际上可以将经过\((v,u)\)的流量交换来抵消成单向流量。
退流操作带来的「抵消」效果使得我们无需担心我们按照「错误」的顺序选择了增广路。

接下类就是对Ford-Fulkerson增广算法的实现了,对于 Ford–Fulkerson 增广的不同实现,时间复杂度也各不相同。其中较主流的实现有 Edmonds–Karp, Dinic, SAP, ISAP 等算法,我会将自己理解了一些的算法介绍出来,至于其他的,下次一定(doge。

EK算法(Edmonds–Karp)

算法思想

EK算法就是通过BFS不断寻找增广路并不断更新最大流,直至在网络中再也找不到增广路为止。上面讲过,仅仅进行增广操作的话,最后得到的答案是错误的,因此需要退流,而为了追求速度,我们希望能在最短时间找到反向边,我们发现,利用邻接矩阵可以快速的找到反向边,因为邻阶矩阵就具有对称性,但是因为邻接矩阵相当于一个二维数组,无论是空间上还是时间上都不是很好,因此在网络流中的通常使用链式前向星来存储。其中,一个常用的技巧是,我们令边从偶数(通常为 0)开始编号,并在加边时总是紧接着加入其反向边使得它们的编号相邻。由此,我们可以令编号为 \(i\) 的边和编号为 $i \oplus 1 $的边始终保持互为反向边的关系。

存边代码如下:

inline void addedge(int u,int v,int c){
	edge[++tot].c = c;
	edge[tot].to = v;
	edge[tot].from = head[u];
	head[u] = tot;
	edge[++tot].c = 0;
	edge[tot].to = u;
	edge[tot].from = head[v];
	head[v] = tot;
}

更新代码如下:

inline void update(){
	int x = t;
	while (x != s){
		int v = pre[x];
		edge[v].c -= dis[t]; //c是剩余容量
		edge[v^1].c += dis[t];
		x = edge[v^1].to;
	}
	ans += dis[t];
}

适用范围

时间复杂度为\(O(nm^2)\),一般能处理\(10^3 ~ 10^4\)规模的网络。

完整代码 \(Code\)

#include <bits/stdc++.h>
#define MAXN 505050
#define int long long
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char c = getchar();
	while (!isdigit(c)){
		if (c == '-'){
			f = -1;
		}
		c = getchar();
	}
	while (isdigit(c)){
		x = x*10+c-'0';
		c = getchar();
	}
	return x*f;
}
int n,m,s,t;
int tot = 1,vis[MAXN],pre[MAXN],head[MAXN],flag[2500][2500];
int dis[MAXN];
int ans;
struct node {
	int from,to,c,flow;
}edge[MAXN];
inline void addedge(int u,int v,int c){
	edge[++tot].to = v;
	edge[tot].from = head[u];
	edge[tot].c = c;
	head[u] = tot;
	edge[++tot].to = u;
	edge[tot].from = head[v];
	edge[tot].c = 0;
	head[v] = tot;
}
inline bool bfs(){
	for (int i=1;i<=n;i++){
		vis[i] = 0;
	}
	queue<int> q;
	q.push(s);
	vis[s] = 1;
	dis[s] = 2005020600;
	while (!q.empty()){
		int x = q.front();
		q.pop();
		for (int i=head[x];i;i=edge[i].from){
			int v = edge[i].to;
			if (!edge[i].c) continue;
			if (vis[v]) continue;
			dis[v] = min(dis[x],edge[i].c);
			pre[v] = i;
			q.push(v);
			vis[v] = 1;
			if (v == t)return 1;
		}
	}
	return 0;
}
inline void update(){
	int x = t;
	while (x!=s){
		int v = pre[x];
		edge[v].c-=dis[t];
		edge[v^1].c+=dis[t];
		x = edge[v^1].to;
	}
	ans+=dis[t];
}
signed main(){
	n = read(),m = read(),s = read(),t = read();
	for (int i=1;i<=m;i++){
		int u = read(),v = read(),w = read();
		if (flag[u][v] == 0){
			addedge(u,v,w);
			flag[u][v] = tot;
		}
		else {
			edge[flag[u][v]-1].c+=w;
		}
	}
	while (bfs()!=0){
		update();
	}
	cout<<ans<<'\n';
	return 0;
}

Dinic算法

Dinic算法在大部分图上的效率都十分优秀,因此也算是处理网络流问题中较为常见的,其时间复杂度是\(O(|V|^2|E|)\) 因为本人太菜不会证明,故不列出证明(

算法思想

EK算法可能会遍历整个残量网络(所有边均为其残量的 \(G_f(V,E_f)\)),而只为寻找一条增广路,显然只开了单线程,那么我们能不能找到一种方法,像开多线程一样,一次寻找多条增广路呢?

这时候就要我们的Dinic算法出马了。

分层图&\(DFS\)

考虑在进行增广前先对网络进行\(BFS\)分层,即根据节点到源点的的距离(dis)把节点分成若干层。对于每一个节点\(u\),我们用\(d(u)\)来表示他的层次,即\(s\)\(x\)的最少边数,在残量网络中,满足\(d(u) = d(u)+1\)的边\((u,v)\)构成的子图被称为分层图,而分层图显然是一张有向无环图(Directed acyclic graph),为什么要构建分层图呢?在解释原因前,要先了解Dinic算法还要通过\(DFS\)进行增广,在\(DFS\)中,从\(S\)开始,每次我们从下一层的随机一个点开始跑,就这样一直跑到汇点,然后再一层层回溯回去,继续找这一层的另外的点再往下搜索,显然的,这样操作可以保证同时多增广的需求,然后对于每一层的最大流我们再进行合并,就能求出该网络图的最大流了。

算法框架

1.在残量网络上BFS求出节点的层次,构造分层图

2.在分层图上DFS寻找增广路,在回溯时同时更新边权

适用范围

时间复杂度为\(O(n^2m)\),一般能处理\(10^4 ~ 10^5\)的网络。

同时,Dinic算法还能用来求二分图,复杂度比匈牙利算法低,但还没学,所以在这就不介绍了。

代码 \(Code\)

#include <bits/stdc++.h>
using namespace std;
const long long inf=2005020600;
int n,m,s,t,u,v;
long long w,ans,dis[520010];
int tot=1,now[520010],head[520010]; 

struct node {
	int to,net;
	long long val;
} e[520010];

inline void add(int u,int v,long long w) {
	e[++tot].to=v;
	e[tot].val=w;
	e[tot].net=head[u];
	head[u]=tot;
	
	e[++tot].to=u;
	e[tot].val=0;
	e[tot].net=head[v];
	head[v]=tot;
}

inline int bfs() {  //在惨量网络中构造分层图 
	for(register int i=1;i<=n;i++) dis[i]=inf;
	queue<int> q;
	q.push(s);
	dis[s]=0;
	now[s]=head[s];
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		for(register int i=head[x];i;i=e[i].net) {
			int v=e[i].to;
			if(e[i].val>0&&dis[v]==inf) {
				q.push(v);
				now[v]=head[v];
				dis[v]=dis[x]+1;
				if(v==t) return 1;
			}
		}
	}
	return 0;
}

inline int dfs(int x,long long sum) {  //sum是整条增广路对最大流的贡献
	if(x==t) return sum;
	long long k,res=0;  //k是当前最小的剩余容量 
	for(register int i=now[x];i&&sum;i=e[i].net) {
		now[x]=i;  //当前弧优化 
		int v=e[i].to;
		if(e[i].val>0&&(dis[v]==dis[x]+1)) {
			k=dfs(v,min(sum,e[i].val));
			if(k==0) dis[v]=inf;  //剪枝,去掉增广完毕的点 
			e[i].val-=k;
			e[i^1].val+=k;
			res+=k;  //res表示经过该点的所有流量和(相当于流出的总量) 
			sum-=k;  //sum表示经过该点的剩余流量 
		}
	}
	return res;
}

int main() {
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);
	}
	while(bfs()) {
		ans+=dfs(s,inf);  //流量守恒(流入=流出) 
	}
	printf("%lld",ans);
	return 0;
}

费用流

费用流就是最小费用最大流,意思就是在之前的情景下,对于每一根管道,我们都给他加上一个代价,就是每经过一条管道都会花费相应的费用,然后你要在这其中找到一条保证最小花费的最大流。

聪明的读者是不是已经大概知道怎么做了呢文章来源地址https://www.toymoban.com/news/detail-558453.html

到了这里,关于网络流学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何为前端编写单元测试?从这篇入门指南开始学习!

    前言 对于现在的前端工程,一个标准完整的项目,通常情况单元测试是非常必要的。但很多时候我们只是完成了项目而忽略了项目测试。我认为其中一个很大的原因是很多人对单元测试认知不够,因此我写了这边文章,一方面期望通过这篇文章让你对单元测试有一个初步认识

    2024年02月01日
    浏览(46)
  • 【译】GPT-4 没有弄清楚事情,但它已经知道了

    原作:史蒂夫·纽曼 引子:它是一只随机鹦鹉,但大多数时候你也是如此,而且它记住的东西比你多得多        关于ChatGPT已经有无数的笔墨了。然而,大部分关注点要么是非常短期和战术性的(“从 ChatGPT 获得出色营销文案的八个魔法提示”),要么是非常长期和理论性的

    2024年01月21日
    浏览(74)
  • 【web知识清单】你想要的都有:网络、HTTP、会话保持、认证授权......持续更新中

    作者简介: 目录 1.网络 2.HTTP 2.1.报文结构 2.1.1.请求报文 2.1.2.响应报文 2.2.方法 2.3.HTTPS 2.4.跨域 3.会话保持 3.1.概述 3.2.cookie 3.3.session 4.认证授权 4.1.Token 4.2.JWT 4.3.oauth 计算机网络: 计算机网络,由节点和边组成的一组拓扑结构。 边,即链路,路由器间的链路为主干链路,路由

    2024年02月10日
    浏览(38)
  • OpenCV: 图像缩放(cv2.resize)【一分钟弄清楚】

    OpenCV: 图像缩放(cv2.resize)【一分钟弄清楚】 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 👈 希望得到您的订阅和支持~ 💡 创作高质量博文,分享更多关于深度学习、PyTorch、Python领域的优质

    2024年04月16日
    浏览(45)
  • 为了弄清起点小说如何算字扣钱,我特意注册了作家账号

    闲来无事,想起这些年也算给起点贡献了不少流量和金钱。 在起点的会员订阅规则里,如下图所示,重点关注2,3和6点,一个是怎么算钱的,一个是怎么算字数的。 计费跟字数相关,归根结底,都是怎么算钱的事儿。 “作品字数以起点计数系统为准。” 起点没有公布计数系

    2024年02月02日
    浏览(32)
  • 想要学习云计算,不知道如何开始?我来说下云计算的学习流程,分享一些学习资源。

    想学习云计算,我们先来搞清到底什么是云计算,接下来我会写清楚云计算是什么,带大家搞清楚这个概念,再写学云计算有哪些途径以及该怎么入门还有系统的学习路线,感兴趣的就看下去吧。 如果有什么问题或者写的不对的地方欢迎在评论区讨论~ 1、什么是云计算 2006年

    2023年04月09日
    浏览(38)
  • 6分钟弄清啥叫“三层交换”(每天学一招,积少成多)

    内容预知 目录 1. 三层交换机的概念及相关内容 三层交换的通信原理: 2.三层交换的工作模拟 第一步: ​编辑  第二步:  对交换机2号设置:  第三步:  总结:            三层交换机 就是具有部分 路由器 功能的交换机,工作在OSI网络标准模型的第三层: 网络层 。三

    2024年02月03日
    浏览(31)
  • 如何学习网络安全?(零基础入门网络安全学习笔记)

    概括来说,网络安全课程的主要内容包括: 安全基本知识 应用加密学 协议层安全 Windows安全(攻击与防御) Unix/Linux安全(攻击与防御) 防火墙技术 入侵监测系统 审计和日志分析 下面分别对每部分知识介绍相应的具体内容和一些参考书。 一、安全基本知识 这部分的学习过

    2024年02月11日
    浏览(47)
  • 【计算机网络详解】——网络层(学习笔记)

    📖 前言:网络层它承担着网络间的数据传输和路由选择等核心任务,通过在传输层协议的基础上添加了路由和转发等功能,使得数据能够在全球范围内的互联网中自由流动。在这篇博客中,我们将深入探讨网络层的工作原理和具体实现,了解其对于现代计算机网络应用的重

    2024年02月10日
    浏览(42)
  • 字节8年测试经验,送给想要学习自动化测试的同学6条建议

    我的职业生涯开始和大多数测试人一样,开始接触都是纯功能界面测试。那时候在一家电商公司做测试,做了有一段时间,熟悉产品的业务流程以及熟练测试工作流程规范之后,效率提高了,工作比较轻松,也得到了更好的机会去发展。 到后来进阶自动化测试,再到测试开发

    2023年04月16日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包