scipy.sparse.csgraph.

maximum_flow#

scipy.sparse.csgraph.maximum_flow(csgraph, source, sink)#

最大化图中两个顶点之间的流量。

1.4.0 版本新增。

参数:
csgraphcsr_array

表示有向图的方阵,其 (i, j)'th 条目是一个整数,表示顶点 i 和 j 之间边的容量。

sourceint

流量流出的源顶点。

sinkint

流量流入的汇顶点。

method: {‘edmonds_karp’, ‘dinic’}, 可选

用于计算最大流的方法/算法。 支持以下方法,

  • ‘edmonds_karp’:[1] 中的 Edmonds Karp 算法。

  • ‘dinic’:[4] 中的 Dinic 算法。

默认值为 ‘dinic’。

1.8.0 版本新增。

返回值:
resMaximumFlowResult

MaximumFlowResult 表示的最大流,其中包括 flow_value 中的流量值和 flow 中的流量图。

引发:
TypeError

如果输入图不是 CSR 格式。

ValueError

如果容量值不是整数,或者源或汇超出范围。

注释

这解决了给定有向加权图上的最大流问题:流量将值(也称为流量)与每条边相关联,该值小于边的容量,因此对于每个顶点(源顶点和汇顶点除外),总流入量等于总流出量。 流量的值是离开源顶点的所有边的流量之和,最大流量问题包括找到其值最大的流量。

根据最大流最小割定理,流量的最大值也是最小割中边的总权重。

为了解决这个问题,我们提供了 Edmonds-Karp [1] 和 Dinic 算法 [4]。 两种算法的实现都力求利用稀疏性。 前者的时间复杂度为 \(O(|V|\,|E|^2)\),其空间复杂度为 \(O(|E|)\)。 后者通过构建级别图并在其中找到阻塞流来实现其性能。 其时间复杂度为 \(O(|V|^2\,|E|)\),其空间复杂度为 \(O(|E|)\)

最大流问题通常定义为具有实数值容量,但我们要求所有容量都是整数以确保收敛。 在处理有理容量或属于 \(x\mathbb{Q}\) 的容量时,对于某些固定的 \(x \in \mathbb{R}\),可以通过相应地缩放所有容量来将问题简化为整数情况。

解决最大流问题可以用于例如计算机视觉中的图切割优化 [3]

参考文献

[1] (1,2)

Edmonds, J. 和 Karp, R. M. 网络流问题算法效率的理论改进。 1972. Journal of the ACM. 19 (2): pp. 248-264

[2]

Cormen, T. H. 和 Leiserson, C. E. 和 Rivest, R. L. 和 Stein C. 算法导论。 第二版。 2001. MIT Press。

[4] (1,2)

Dinic, Efim A. 网络中最大流问题的解决方案算法与功率估计。 在 Soviet Math. Doklady 中,第 11 卷,第 1277-1280 页。 1970。

示例

也许最简单的流量问题是只有一个边的图,该边从源 (0) 到汇 (1)

(0) --5--> (1)

在这里,最大流量只是边的容量

>>> import numpy as np
>>> from scipy.sparse import csr_array
>>> from scipy.sparse.csgraph import maximum_flow
>>> graph = csr_array([[0, 5], [0, 0]])
>>> maximum_flow(graph, 0, 1).flow_value
5
>>> maximum_flow(graph, 0, 1, method='edmonds_karp').flow_value
5

另一方面,如果在源和汇之间存在瓶颈,则可以减少最大流量

(0) --5--> (1) --3--> (2)
>>> graph = csr_array([[0, 5, 0], [0, 0, 3], [0, 0, 0]])
>>> maximum_flow(graph, 0, 2).flow_value
3

更简单的示例在 [2],第 26.1 章中给出

>>> graph = csr_array([[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]])
>>> maximum_flow(graph, 0, 5).flow_value
23

可以将寻找二分图中的最大匹配的问题简化为最大流问题:令 \(G = ((U, V), E)\) 为二分图。 然后,向图中添加一个源顶点,该顶点具有到 \(U\) 中每个顶点的边,并添加一个汇顶点,该顶点具有来自 \(V\) 中每个顶点的边。 最后,为结果图中每条边赋予容量 1。 然后,新图中的最大流给出了原始图中的最大匹配,该匹配由 \(E\) 中流量为正的边组成。

假设这些边由 \(\lvert U \rvert \times \lvert V \rvert\) CSR 格式的矩阵表示,其 \((i, j)\)'th 条目为 1 如果存在从 \(i \in U\)\(j \in V\) 的边,否则为 0; 也就是说,输入的形式是 maximum_bipartite_matching 所需的形式。 然后,上面构造的图的 CSR 表示包含此矩阵作为一个块。 这是一个例子

>>> graph = csr_array([[0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 1, 0]])
>>> print(graph.toarray())
[[0 1 0 1]
 [1 0 1 0]
 [0 1 1 0]]
>>> i, j = graph.shape
>>> n = graph.nnz
>>> indptr = np.concatenate([[0],
...                          graph.indptr + i,
...                          np.arange(n + i + 1, n + i + j + 1),
...                          [n + i + j]])
>>> indices = np.concatenate([np.arange(1, i + 1),
...                           graph.indices + i + 1,
...                           np.repeat(i + j + 1, j)])
>>> data = np.ones(n + i + j, dtype=int)
>>>
>>> graph_flow = csr_array((data, indices, indptr))
>>> print(graph_flow.toarray())
[[0 1 1 1 0 0 0 0 0]
 [0 0 0 0 0 1 0 1 0]
 [0 0 0 0 1 0 1 0 0]
 [0 0 0 0 0 1 1 0 0]
 [0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 0 0 0]]

此时,我们可以找到添加的汇和添加的源之间的最大流量,并且可以通过将流量函数限制为与原始图相对应的块来获得所需的匹配

>>> result = maximum_flow(graph_flow, 0, i+j+1, method='dinic')
>>> matching = result.flow[1:i+1, i+1:i+j+1]
>>> print(matching.toarray())
[[0 1 0 0]
 [1 0 0 0]
 [0 0 1 0]]

这告诉我们 \(U\) 中的第一个、第二个和第三个顶点分别与 \(V\) 中的第二个、第一个和第三个顶点匹配。

虽然这解决了通常的最大二分匹配问题,但请注意,专门针对该问题的算法,例如 maximum_bipartite_matching,通常会表现更好。

这种方法也可以用于解决最大二分匹配问题的各种常见推广。 例如,如果某些顶点可以与多个其他顶点匹配,则可以通过适当修改新图的容量来处理此问题。