蓝桥杯-网络流裸题

1|0题目:

一个有向图,求1到N的最大流

输入格式:

第一行N M,表示点数与边数

接下来M行每行s、t、c。表示一条从s到t的容量为c的边

输出格式:

一个数最大流量

样例输入:

6 10 1 2 4 1 3 8 2 3 4 2 4 4 2 5 1 3 4 2 3 5 2 4 6 7 5 4 6 5 6 3

样例输出

8

数据约定:

n<=1000 m<=10000

2|0预备知识和注意事项:

考虑如下情境:

在某个污水处理厂的某一道程序里,有一个「进水孔」,和一个「排水孔」,中间由许多「孔径不一」的水管连接起来,因为水管的「孔径大小」会影响到「每单位时间的流量」,因此要解决的问题,就是找到每单位时间可以排放「最大流量( flow )」的「排水方法」。

image-20201010135919914

以图一为例,进水孔为vertex(S),排水孔为vertex(T),中间要经过污水处理站vertex(A)vertex(C)边(edge)代表水管,边的权重(weight)(以下将称为capacity )表示水管的「孔径」。

考虑两种「排水方法」的flow:

  • 第一种分配水流的方法,每单位时间总流量为20

    image-20201010140613238

    • 在Path : S − A − T上每单位时间流了5单位的水;
    • 在Path : S − A − C − T上每单位时间流了10单位的水(问题出在这,占去了edge(C,T)的容量);
    • 在Path : S − C − T上,因为edge(C,T)上只剩下「5单位的容量」,因此每单位时间流了5单位的水。
  • 第二种分配水流的方法,每单位时间总流量为25

    image-20201010140839607

    • 在Path : S − A − T上每单位时间流了10单位的水;
    • 在Path : S − A − C − T上每单位时间流了5单位的水;
    • 在Path : S − C− T上,因为edge(C,T)上刚好还有「10单位的容量」,因此每单位时间流了10单位的水;

从以上两种「排水方式」可以看得出来,解决问题的精神,就是如何有效利用水管的「孔径容量」,让最多的水可以从「进水孔」流到「排水孔」。

这就是在网络流(Flow Networks)上找到最大流量( Maximum Flow )的问题。

3|0网络流的基本性质

Flow Networks是一个带权有向图,其edge(X,Y)具有非负的capacity,即:c(X,Y)≥0,如图二(a)。我们可以利用一个矩阵存储图信息。

image-20201010141311185

  1. 若不存在edge(X,Y),则定义c(X,Y) = 0
  2. 特别的,要区分两个vertex
    1. source:表示Flow Networks的流量源头,以s表示。
    2. sink/termination:表示Flow Networks的流量终点,以t表示。
  3. 水管里的水流:flow,必须满足以下三个条件a.容量限制 b.反对称性 c.流守恒性
    1. 从顶点X流向顶点Y的流 <= edge(X,Y)capacity
      1. 以图二(b)为例,在Path : S − A − C − D − T上的edge之capacity皆大于6,因此在此路径上流入6单位的flow是可行的。最小的f(X,Y) = 7,所以流过的flow只要小于等于7即可。
    2. f(X,Y) = -f(Y,X),此与电子流(负电荷)与电流(正电荷)的概念雷同
    3. 对有向图中除了sourcesink以外的顶点而言,所有「流进flow」之总和 = 所有「流出flow」的总和。也就是水流不会无故增加或无故减少,可视为一种守恒。
  4. 可行流:在容量网络中满足以下条件的网络流flow,成为可行流
    1. 弧流量限制条件:0 <= f(u,v) <= c(u,v)
    2. 平衡条件:即流入一个点的流量要等于流出这个点的流量。(sourcesink除外)

4|0最大流量算法(Ford-Fulkerson算法)

Ford-Fulkerson算法需要两个辅助工具

  • Residual Networks(剩余网路,残差图)
  • Augmenting Paths(增广路径)

4|1Residual Networks(剩余网路,残差图)

Residual Networks的概念为:记录Graph上之edge还有多少「剩余的容量」可以让flow流过。

image-20201010143003465

以图三为例:

  • 如果在Path:S - A - C - D - T上所有的edge都有6单位的flow流过,那么这些edge(edge(S,A)、edge(A,C)、edge(C,D)、edge(D,T))可用的剩余capacity,都应该减6。例如:edge(S,A)只能在容纳9-6=3单位的flow,edge(C,D)只能容纳7-6=1单位的flow。

  • 最关键的是,若「从vertex(A)指向vertex(C )」之edge(A,C)上,有6单位的flow流过,即f(A,C)=6,那么在其Residual Networks上,会因应产生出一条「从vertex(C ) 指向vertex(A)」的edge(C,A),并具有6单位的residual capacity,即:cf(C,A) = 6。 (证明见下)

  • 证明:这些residual capacity称为:剩余capacitycf表示。

    • cf(C,A) = c(C,A) - f(C,A) = c(C,A) + f(A,C) = 0+6 = 6

    • 其物理意义:可以用重置配置水流方向来理解。

      image-20201010144230200

    • 根据上图表示,我们可以将其看成是:我们已经有了一个通过6个单位的流量的剩余网络,如果现在想经过Path:S - C - A - B - T流过2单位的flow。

    • 根据上图画出的残差图为:

      image-20201010144728020

    • 在图三(a)已经有6单位的流从顶点A流向顶点C,现在可以从edge(A,C)上把2单位的flow"收回",转而分配到edge(A,B)上,而edge(A,C)上就只剩下4单位的流,最后的结果如下图所示:

      image-20201010151731653

      我们根据上图可以看出:流入sink (或称termination)的flow累加到8单位。

  • 综上:

    • 若edge(X,Y)上有flow流过,即f(X,Y),便将edge(X,Y)上的Residual Capacity定义为:cf(X,Y) = c(X,Y) - f(X,Y)
    • c(X,Y)表示原来水管孔径大小;
    • f(X,Y)表示目前水管已经有多少容量;
    • cf(X,Y)表示水管还能在容纳多少流量;

4|2Augmenting Paths(增广路径)

Residual Networks里,所有能够「从source走到termination」的路径,也就是所有能够「增加flow的path」,就称为Augmenting Paths

4|3演算法

Ford-Fulkerson Algorithm (若使用BFS搜寻路径,又称为Edmonds-Karp Algorithm)的方法如下:

  1. Residual Networks上寻找Augmenting Paths
    1. 若以BFS方法寻找,便能确保每次找到的Augmenting Paths一定经过最少的edge。(对于所有边长度相同的情况,比如地图模型,bfs第一次遇到目标点,此时就一定是从根节点到目标节点最短的路径【因为每一次所有点都是向外扩张一步,你先遇到就一定是最短】。bfs先找到的一定是最短的)。
  2. 找到Augmenting Paths上的最小Residual Capacity加入总flow,在以最小Residual Capacity更新Residual Networks的edge的Residual Capacity
  3. 重复上述步骤,知道再也没有Augmenting Paths为止,便能找到最大流。

5|0例子:

STEP-1:先用flow = 0Residual Capacity进行初始化,如图五(a)

image-20201010153313334

STEP-2:在Residual Networks上寻找Augmenting Paths

在该Graph中,使用BFS寻找能够从顶点S到顶点T,且edge数最少的路径,PATH = S - A - B - T,见图五(b)。BFS有可能找到其他一条S - C - D - T,这里以前者为例:

image-20201010153720513

STEP-3:找到Augmenting Paths上的最小Residual Capacity加入总flow

最小Residual Capacity = 3;

flow = flow + 3;

STEP-4:以最小Residual Capacity更新Residual Networks上的edge之residual capacity

cf(S,A) = c(S,A) - f(S,A) = 9 - 3 = 6; cf(A,S) = c(A,S) - f(A,S) = 0 + 3 = 3; cf(A,B) = c(A,B) - f(A,B) = 3 - 3 = 0; cf(B,A) = c(B,A) - f(B,A) = 0 + 3 = 3; cf(B,T) = c(B,T) - f(B,T) = 9 - 3 = 6; cf(T,B) = c(T,B) - f(T,B) = 0 + 3 = 3;

image-20201010154328576

重复上述操作,对上述残差图继续寻找增广路径,直到找不到增广路径为止。

6|0代码:

  1. 建立有向图Graph,并使用graph[X][Y]保存edge(X,Y)的权重weight

    private static void buildGraph(int[][] graph, int vertex1, int vertex2, int weight){ // 因为一条边可能会出现多次 graph[vertex1][vertex2] += weight; }
  2. 使用BFS方法进行搜索,寻找从sourcesink的路径,而且是edge数量最少的路径:

    private static boolean BFSFindPath(int[][] graph, int source, int sink, int[] path) { //path[]是通过记录每个节点的父节点,从而记录下一条完整的路径 //每次寻找都要初始化一次path for(int i = 0; i < path.length; i++) { path[i] = 0; } int vertex_num = graph.length - 1; boolean[] visited = new boolean[vertex_num + 1]; Queue<Integer> queue = new LinkedList<Integer>(); queue.offer(source); visited[source] = true; while(queue.isEmpty() == false) { int temp = queue.poll(); for(int i = 1; i <= vertex_num; i++) { if (graph[temp][i] > 0 && visited[i] == false) { queue.offer(i); visited[i] = true; path[i] = temp; } } } return visited[sink] == true; }
  3. 找到从BFSFindPath()找到的路径上,最小的Residual capacity

    private static int minCapacity(int[] path, int[][] graph) { int min = graph[path[path.length - 1]][path.length - 1]; for (int i = path.length - 2; i != 1; i = path[i]) { if (graph[path[i]][i] < min && graph[path[i]][i] > 0) { //如果不是>0则可能把没有边的也算进去。 min = graph[path[i]][i]; } } return min; }
  4. 演算法的思路:

    int max_flow = 0; int[] path = new int[vertex_num + 1]; // 在Residual Networks上寻找Augmenting Path while(BFSFindPath(graph, 1, vertex_num, path)) { //如果能够找到Augmenting Path,那么就在该路径上寻找最小容量 int min_capacity = minCapacity(path, graph); //更新最大流 max_flow += min_capacity; // 更新残差图 for(int i = vertex_num; i != 1; i = path[i]) { int j = path[i]; graph[j][i] -= min_capacity; graph[i][j] += min_capacity; } } System.out.println(max_flow);
  5. 完整代码:

    import java.util.LinkedList; import java.util.Queue; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //第一行输入 节点个数 和 边的个数 int vertex_num = sc.nextInt(); int edge_num = sc.nextInt(); //初始化二维数组进行存放数据 int[][] graph = new int[vertex_num + 1][vertex_num + 1]; //每一行输入进行保存数据 for(int i = 0; i < edge_num; i++) { int vertex1 = sc.nextInt(); int vertex2 = sc.nextInt(); int weight = sc.nextInt(); // 填充数据,形成一个有向图 buildGraph(graph, vertex1, vertex2, weight); } // 声明最大流和Augmenting Path int max_flow = 0; int[] path = new int[vertex_num + 1]; // 在Residual Networks上寻找Augmenting Path while(BFSFindPath(graph, 1, vertex_num, path)) { //如果能够找到Augmenting Path,那么就在该路径上寻找最小容量 int min_capacity = minCapacity(path, graph); //更新最大流 max_flow += min_capacity; // 更新残差图 for(int i = vertex_num; i != 1; i = path[i]) { int j = path[i]; graph[j][i] -= min_capacity; graph[i][j] += min_capacity; } } System.out.println(max_flow); } private static int minCapacity(int[] path, int[][] graph) { int min = graph[path[path.length - 1]][path.length - 1]; for (int i = path.length - 2; i != 1; i = path[i]) { if (graph[path[i]][i] < min && graph[path[i]][i] > 0) { //如果不是>0则可能把没有边的也算进去。 min = graph[path[i]][i]; } } return min; } /** * 建立有向图 */ private static void buildGraph(int[][] graph, int vertex1, int vertex2, int weight){ // 因为一条边可能会出现多次 graph[vertex1][vertex2] += weight; } private static boolean BFSFindPath(int[][] graph, int source, int sink, int[] path) { //path[]是通过记录每个节点的父节点,从而记录下一条完整的路径 //每次寻找都要初始化一次path for(int i = 0; i < path.length; i++) { path[i] = 0; } int vertex_num = graph.length - 1; boolean[] visited = new boolean[vertex_num + 1]; Queue<Integer> queue = new LinkedList<Integer>(); queue.offer(source); visited[source] = true; while(queue.isEmpty() == false) { int temp = queue.poll(); for(int i = 1; i <= vertex_num; i++) { if (graph[temp][i] > 0 && visited[i] == false) { queue.offer(i); visited[i] = true; path[i] = temp; } } } return visited[sink] == true; } }

__EOF__

本文作者SalemG
本文链接https://www.cnblogs.com/guosiliang/p/13794279.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   SalemG  阅读(235)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示