蓝桥杯-网络流裸题
题目:
一个有向图,求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
预备知识和注意事项:
考虑如下情境:
在某个污水处理厂的某一道程序里,有一个「进水孔」,和一个「排水孔」,中间由许多「孔径不一」的水管连接起来,因为水管的「孔径大小」会影响到「每单位时间的流量」,因此要解决的问题,就是找到每单位时间可以排放「最大流量( flow )」的「排水方法」。

以图一为例,进水孔为vertex(S),排水孔为vertex(T),中间要经过污水处理站vertex(A)与vertex(C)边(edge)代表水管,边的权重(weight)(以下将称为capacity )表示水管的「孔径」。
考虑两种「排水方法」的flow:
-
第一种分配水流的方法,每单位时间总流量为
20:
- 在Path :
S − A − T上每单位时间流了5单位的水; - 在Path :
S − A − C − T上每单位时间流了10单位的水(问题出在这,占去了edge(C,T)的容量); - 在Path :
S − C − T上,因为edge(C,T)上只剩下「5单位的容量」,因此每单位时间流了5单位的水。
- 在Path :
-
第二种分配水流的方法,每单位时间总流量为
25:
- 在Path :
S − A − T上每单位时间流了10单位的水; - 在Path :
S − A − C − T上每单位时间流了5单位的水; - 在Path :
S − C− T上,因为edge(C,T)上刚好还有「10单位的容量」,因此每单位时间流了10单位的水;
- 在Path :
从以上两种「排水方式」可以看得出来,解决问题的精神,就是如何有效利用水管的「孔径容量」,让最多的水可以从「进水孔」流到「排水孔」。
这就是在网络流(Flow Networks)上找到最大流量( Maximum Flow )的问题。
网络流的基本性质
Flow Networks是一个带权有向图,其edge(X,Y)具有非负的capacity,即:c(X,Y)≥0,如图二(a)。我们可以利用一个矩阵存储图信息。

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

以图三为例:
-
如果在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称为:剩余capacity以cf表示。-
cf(C,A) = c(C,A) - f(C,A) = c(C,A) + f(A,C) = 0+6 = 6 -
其物理意义:可以用重置配置水流方向来理解。

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

-
在图三(a)已经有
6单位的流从顶点A流向顶点C,现在可以从edge(A,C)上把2单位的flow"收回",转而分配到edge(A,B)上,而edge(A,C)上就只剩下4单位的流,最后的结果如下图所示:
我们根据上图可以看出:流入
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)表示水管还能在容纳多少流量;
- 若edge(X,Y)上有flow流过,即f(X,Y),便将edge(X,Y)上的
Augmenting Paths(增广路径)
在Residual Networks里,所有能够「从source走到termination」的路径,也就是所有能够「增加flow的path」,就称为Augmenting Paths。
演算法
Ford-Fulkerson Algorithm (若使用BFS搜寻路径,又称为Edmonds-Karp Algorithm)的方法如下:
- 在
Residual Networks上寻找Augmenting Paths- 若以BFS方法寻找,便能确保每次找到的
Augmenting Paths一定经过最少的edge。(对于所有边长度相同的情况,比如地图模型,bfs第一次遇到目标点,此时就一定是从根节点到目标节点最短的路径【因为每一次所有点都是向外扩张一步,你先遇到就一定是最短】。bfs先找到的一定是最短的)。
- 若以BFS方法寻找,便能确保每次找到的
- 找到
Augmenting Paths上的最小Residual Capacity加入总flow,在以最小Residual Capacity更新Residual Networks的edge的Residual Capacity。 - 重复上述步骤,知道再也没有
Augmenting Paths为止,便能找到最大流。
例子:
STEP-1:先用flow = 0对Residual Capacity进行初始化,如图五(a)

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

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;

重复上述操作,对上述残差图继续寻找增广路径,直到找不到增广路径为止。
代码:
-
建立有向图Graph,并使用
graph[X][Y]保存edge(X,Y)的权重weightprivate static void buildGraph(int[][] graph, int vertex1, int vertex2, int weight){ // 因为一条边可能会出现多次 graph[vertex1][vertex2] += weight; } -
使用BFS方法进行搜索,寻找从
source到sink的路径,而且是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; } -
找到从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; } -
演算法的思路:
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); -
完整代码:
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; } }

浙公网安备 33010602011771号