网络流——最大流(全)_网络最大流-程序员宅基地

最大流

先从最基础的最大流开始:

何为最大流问题?

简单来说就是水流从一个源点s通过很多路径,经过很多点,到达汇点t,问你最多能有多少水能够到达t点。
在这里插入图片描述
从s到t经过若干个点,若干条边,每一条边的水流都不能超过边权值(可以小于等于但不能大于),所以该图的最大流就是10+22+45=77。

如果你还是不能理解,我们就换一种说法,假设s城有inf个人想去t城,但是从s到t要经过一些城市才能到达,(以上图为例)其中s到3城的火车票还剩10张,3到t的火车票还剩15张,其他路以此类推,问最终最多能有多少人能到达t城?(假设这个地区只有火车,没有汽车飞机,步行和骑自行车会累死就不考虑了,再假设所有人都买得起火车票。)

那怎么么解决这个问题呢?

对于这个问题,刚看到时,你有什么想法?

或许你会有一个这样的思路:从s开始找所有能到达t的路径,然后找每条路径上权值最小的边(或者说能承受水流最小的管子),再进行其它操作,就能找到这张图的最大流。

然后我们有

EK(Edmond—Karp)算法。
即bfs找增广路法,也就是EK法,全名是Edmond-Karp
流量,容量和可行流
对于弧(u,v)来说,流量就是其上流过的水量(我们通常用f(u,v)表示),而容量就是其上可流过的最大水量(我们通常用c(u,v)表示),只要满足f(u,v)<=c(u,v),我们就称流量f(u,v)是可行流(对于最大流问题而言,所有管道上的流量必须都是可行流)。
增广路
在这里插入图片描述
如果一条路上的所有边均满足:

正向边: f(u,v)< c(u,v) ——– 反向边:f(u,v)> 0
前向弧都是非饱和弧

则我们称这条路径为一条增广路径,简称增广路。

简单路径指路径上的顶点都不相同的路径;

那么求最大流问题可以转换为不断求解增广路的问题,并且,显然当图中不存在增广路时就达到了最大流。我们不断地从起点开始寻找增广路,每次都对其进行增广,直到源点和汇点不连通,也就是找不到增广路为止。当找不到增广路的时候,当前的流量就是最大流,这个结论非常重要。

首先,假如所有边上的流量都没有超过容量(不大于容量),那么就把这一组流量,或者说,这个流,称为一个可行流。一个最简单的例子就是,零流,即所有的流量都是0的流。
我们就从这个零流开始考虑,假如有这么一条路,这条路从源点开始一直一段一段的连到了汇点,并且,这条路上的每一段都满足流量<容量,注意,是严格的<,而不是<=。那么,我们一定能找到这条路上的每一段的(容量-流量)的值当中的最小值delta。我们把这条路上每一段的流量都加上这个delta,一定可以保证这个流依然是可行流,这是显然的。

这样我们就得到了一个更大的流,他的流量是之前的流量+delta,而这条路就叫做增广路。
源点:有n个点,有m条有向边,有一个点很特殊,只出不进,叫做源点。
汇点:另一个点也很特殊,只进不出,叫做汇点。

具体怎么操作呢?

其实很简单,直接从s到t广搜即可,从s开始不断向外广搜,通过权值大于0的边(因为后面会减边权值,所以可能存在边权为0的边),直到找到t为止,然后找到该路径上边权最小的边,记为mi,然后最大流加mi,然后把该路径上的每一条边的边权减去mi,直到找不到一条增广路(从s到t的一条路径)为止。(为什么要用mi呢?你要争取在这条路上多走更多人,但又不能让人停在某个城市)

具体操作:

找增广路:
寻找增广路的时候我们可以简单的从源点开始做bfs,并不断修改这条路上的delta量,直到找到源点或者找不到增广路。
这里要先补充一点,在程序实现的时候,我们通常只是用一个c数组来记录容量,而不记录流量,当流量+1的时候,我们可以通过容量-1来实现,以方便程序的实现。

int BFS()
{
    
    int i,j,k,v,u;
    memset(pre,-1,sizeof(pre));
    for(i=1;i<=n;++i)flow[i]=max_int; 
    queue<int>que;
    pre[start]=0;
    que.push(start);
    while(!que.empty())
    {
    
        v=que.front();
        que.pop(); 
        for(i=1;i<=n;++i)
        {
    
            u=i;
            if(u==start||pre[u]!=-1||map[v][u]==0)continue;
            pre[u]=v;
            flow[u]=MIN(flow[v],map[v][u]);
            que.push(u);
        }
    }
    if(flow[end]==max_int)return -1;
    return flow[end];
}

但事实上并没有这么简单,上面所说的增广路还不完整,比如说下面这个网络流模型。
在这里插入图片描述
我们第一次找到了1-2-3-4这条增广路,这条路上的delta值显然是1。于是我们修改后得到了下面这个流。(图中的数字是容量)
在这里插入图片描述
这时候(1,2)和(3,4)边上的流量都等于容量了,我们再也找不到其他的增广路了,当前的流量是1。

但这个答案明显不是最大流,因为我们可以同时走1-2-4和1-3-4,这样可以得到流量为2的流。

那么我们刚刚的算法问题在哪里呢?问题就在于我们没有给程序一个”后悔”的机会,应该有一个不走(2-3-4)而改走(2-4)的机制。那么如何解决这个问题呢?回溯搜索吗?那么我们的效率就上升到指数级了。

而这个算法神奇的利用了一个叫做反向边的概念来解决这个问题。即每条边(I,j)都有一条反向边(j,i),反向边也同样有它的容量。

我们直接来看它是如何解决的:

在第一次找到增广路之后,在把路上每一段的容量减少delta的同时,也把每一段上的反方向的容量增加delta。即在Dec(c[x,y],delta)的同时,inc(c[y,x],delta)

我们来看刚才的例子,在找到1-2-3-4这条增广路之后,把容量修改成如下
在这里插入图片描述
这时再找增广路的时候,就会找到1-3-2-4这条可增广量,即delta值为1的可增广路。将这条路增广之后,得到了最大流2。
事实上,当我们第二次的增广路走3-2这条反向边的时候,就相当于把2-3这条正向边已经是用了的流量给”退”了回去,不走2-3这条路,而改走从2点出发的其他的路也就是2-4。(有人问如果这里没有2-4怎么办,这时假如没有2-4这条路的话,最终这条增广路也不会存在,因为他根本不能走到汇点)同时本来在3-4上的流量由1-3-4这条路来”接管”。而最终2-3这条路正向流量1,反向流量1,等于没有流量。

这就是这个算法的精华部分,利用反向边,使程序有了一个后悔和改正的机会。而这个算法和我刚才给出的代码相比只多了一句话而已。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43838785/article/details/102879239

智能推荐

Unity 像在Game里面移动摄像机 平移旋转缩放_unity 在game移动画面-程序员宅基地

文章浏览阅读853次。using UnityEngine;public class FreeCamera : MonoBehaviour{ public float lookSpeedH = 2f; public float lookSpeedV = 2f; public float zoomSpeed = 20f; public float dragSpeed = 100f; private float yaw = 0f; private float pitch = 0_unity 在game移动画面

MAC 安装JDK(附JDK下载地址)_mac jdk-程序员宅基地

文章浏览阅读3.8w次,点赞24次,收藏101次。最新版Mac安装JDK并配置系统环境变量教程,1.双击运行下载好的JDK安装文件。_mac jdk

@RequestBody 的使用_@jsonalias不生效-程序员宅基地

文章浏览阅读611次。添加链接描述总结:@RequestBody 主要是用来解析前端传过来的参数是放在请求体中,并且 Content-Type 是 application/json 类型的数据。1、@JsonAlias 注解实现:json 转模型时,使 json 中的特定 key 能转化为特定的模型属性;但是模型转 json 时,对应的转换后的 key 仍然与属性名一致。举个栗子:/** * 姓名 */@JsonAlias(value = {"Name", "name123"})private Strin_@jsonalias不生效

java duration 时间差_Java Duration toDays()用法及代码示例-程序员宅基地

文章浏览阅读1k次。java.time包中的Duration Class的toDays()方法用于获取此持续时间的天数。用法:public long toDays()参数:此方法不接受任何参数。返回值:此方法返回一个long值,该值是此持续时间中的天数。以下示例说明了Duration.toDays()方法:示例1:// Java code to illustrate toDays() methodimport jav..._duration.ofdays(10)

2021-03-05-程序员宅基地

文章浏览阅读110次。Loadrunner安装报错:Microsoft WSE 2.0 SP3.msi链接:https://pan.baidu.com/s/17G39Ef5OQI0eqWaE3wBE9w提取码:ruog

python调用sdk的文章_Python调用大华SDK取图-程序员宅基地

文章浏览阅读830次。之前写了一个大华SDK取图程序,中间遇见很多问题,查找解决办法的时候发现网上关于大华SDK的调用,用Python写的少得可怜,基本没有。记录一下开发中遇见的问题和解决办法。业务场景是识别人脸到人脸的时候保存背景图和人脸图。根据开发文档,流程为初始化SDK,登录设备,订阅智能事件,在智能事件回调函数中可以取到需要的图片信息。声明函数调用python调用C语言函数,需要先声明函数及函数返回值类型参数类..._python调用sdk

随便推点

Unity-Transform与RectTransform区别_unity transform 与rect transform-程序员宅基地

文章浏览阅读587次。UGUI下的canvas的2d对象,是RectTransform组件,3d空间的对象是transfrom组件。RectTransform是Transfrom的子类。Transform包含RectTransform。对于2D的UGUI,使用RectTransform即可,不需要使用Transform。例如:gameObject.transform.position 与gameObject.getComment().position含义是一样的。..._unity transform 与rect transform

Social Chioce-程序员宅基地

文章浏览阅读54次。如果所有人都认为A比B好,WWW 选择了这个序,那么 WWW 就是有效的。Arrow 定理告诉我们,满足有效和IIA的社会福利函数 WWW 只能是独裁的。想要设计出好的社会福利函数,我们必须舍弃PE、IIA的其中一个性质。...

网络基础知识_gpo 1 和2-程序员宅基地

文章浏览阅读663次。1.&nbsp;CPU性能指标中,以下指标代表什么意思?外频:CPU的基准频率,CPU与主板之间同步运行的速度,它决定整个主板的运行速率。前端总线频率:直接影响CPU和内存数据交换的速度。2.&nbsp;主板的两个芯片分别是什么芯片,如何区分?具备什么作用?北桥:离CPU近,负责CPU、内存、显卡之间的通信;南桥:离CPU远,负责I/O总线之间的通信。3.&nbsp;BIOS是什么..._gpo 1 和2

MVC设计模式-程序员宅基地

文章浏览阅读3k次。MVC设计模式_mvc设计模式

unix linux 命令参考,Unix/Linux 命令参考-程序员宅基地

文章浏览阅读56次。文件命令ls �C 列出目录ls -al �C 使用格式化列出隐藏文件cd dir - 更改目录到 dircd �C 更改到 home 目录pwd �C 显示当前目录mkdir dir �C 创建目录 dirrm file �C 删除 filerm -r dir �C 删除目录 dirrm -f file �C 强制删除 filerm -rf dir �C 强制删除目录 dir *cp file1..._unix/linux命令参考

用多线程并发的方式来计算两个矩阵的乘法_、试用线程的方法编写两个10*10矩阵的相乘的计算程序,用10个线程完成结果矩-程序员宅基地

文章浏览阅读1.3w次,点赞3次,收藏23次。要求很简单,计算两个矩阵的乘法。为了加速,这里面使用了pthread库,来并发计算。基本思路如下图。比如用两个线程来计算。矩阵A * B。那么就把A分成两份。比如下图,就是0,2,4和1,3,5这两份。在线程1中计算第0,2,4行和B个列的乘积,在线程2中计算1,3,5行和B各个列的乘积。思路很简单。最后代码如下:// pthread.cpp : Defines the_、试用线程的方法编写两个10*10矩阵的相乘的计算程序,用10个线程完成结果矩