【问题标题】:Finding a perfect matching in graphs在图中找到完美匹配
【发布时间】:2020-05-12 22:28:54
【问题描述】:

我有这个问题:

航空公司有 N 架不同的飞机和 T 名飞行员。每个飞行员都有一份他可以驾驶的飞机清单。每次飞行需要 2 名飞行员。该公司希望同时拥有尽可能多的航班。找到一个算法来判断您是否可以同时拥有所有航班。

我想到的解决方案是在此图上找到最大流量:

我只是不确定容量应该是多少。你能帮我解决这个问题吗?

【问题讨论】:

  • 您能解释一下您的方法吗?
  • 另外,你能分享一下 N 和 T 的约束条件吗?我想我可以为此建议一个动态编程解决方案,如果约束非常大,它可能实际上不起作用。
  • 我的解决方案是构建一个二分图,其中一侧有飞行员,另一侧有飞机。它们之间的边缘将把每个飞行员连接到他可以驾驶的飞机上。问题是我不确定如何定义容量。 N 和 T 没有限制。
  • 您假设这个问题很好地表示为容量问题。是什么让您对此有把握?
  • 听起来像是最大二分匹配的一种特殊情况,其中一侧应该有两个输出节点(一架飞机需要两个飞行员)。我喜欢这个问题。赞成。

标签: algorithm graph mathematical-optimization combinatorics max-flow


【解决方案1】:

找到最大流量的好主意。

  • 对于来自源 --> 飞行员的每条边,分配容量为 1。每个飞行员一次只能驾驶一架飞机,因为他们同时运行。
  • 对于来自 Pilot --> 飞机的每条边,分配容量 1。如果这条边的流量为 1,则表示给定的飞行员正在驾驶该飞机。
  • 对于来自平面 --> 接收器的每条边,分配容量为 2。这表示每个平面必须由正好 2 名飞行员提供。

现在,找到最大流量。如果得到的最大流量是平面数量的两倍,那么就有可能满足约束条件。在这种情况下,处于容量状态的飞机和飞行员之间的边表示匹配。

【讨论】:

  • 你能举个例子吗,拿 5 架飞机和 8 名飞行员。还有一些随意的连接。
【解决方案2】:

另一个答案很好,但您实际上并不需要涉及流,因为这也可以简化为普通的最大二分匹配:

  • 对于每个平面,将另一个辅助平面添加到平面分区,其边缘与第一个平面相同的飞行员。
  • 找到最大二分匹配M
  • 现在当且仅当 M = 2 N 时答案才是正确的。

如果您愿意,您可以将其视为每架飞机需要一名飞行员和一名副驾驶,现在与每架飞机关联的两个顶点代表这两个角色。

最大二分匹配的减少是线性时间,因此使用例如Hopcroft–Karp algorithm找到匹配的,可以解决O(|E| √|V|)里的问题 E 是分区之间的边数,V = T + N

在实践中,使用基于最大流的方法的改进应该取决于您的实现质量以及图表表示的特定选择,但您可能会更好地使用这种方式。

实现示例

为了说明最后一点,让我们来看看这两种减少在实践中的表现。由于其内置内存局部性而经常有用的图的一种表示是CSR matrix,因此让我们假设输入是这样一个矩阵,其行对应于平面,其列对应于飞行员.

我们将使用Python library SciPy,它带有用于最大二分匹配和最大流量的算法,并且可以与引擎盖下的图的 CSR 矩阵表示一起使用。

在上面给出的算法中,我们需要构造图的邻接矩阵,并添加额外的顶点。这只是将输入矩阵堆叠在其自身之上的结果,这在 CSR 数据结构方面很简单:按照维基百科的表示法,COL_INDEX 应该只是重复,并且 ROW_INDEX 应该替换为与副本连接的 ROW_INDEX ROW_INDEX 的所有元素都增加了 ROW_INDEX 的最后一个元素。

在 SciPy 中,对 OP 中的问题回答是或否的完整实现如下所示:

import numpy as np
from scipy.sparse.csgraph import maximum_bipartite_matching

def reduce_to_max_matching(a):
    i, j = a.shape
    data = np.ones(a.nnz * 2, dtype=bool)
    indices = np.concatenate([a.indices, a.indices])
    indptr = np.concatenate([a.indptr, a.indptr[1:] + a.indptr[-1]])
    graph = csr_matrix((data, indices, indptr), shape=(2*i, j))
    return (maximum_bipartite_matching(graph) != -1).sum() == 2 * i

在@HeatherGuarnera 的回答给出的最大流量方法中,我们需要设置新图的完整邻接矩阵。这也相对简单;输入矩阵将显示为邻接矩阵的某个子矩阵,我们需要为源顶点添加一行,为目标添加一列。 SciPy 的最大流量求解器的example section of the documentation 实际上包含了实际情况的说明。采用这个,一个完整的解决方案如下:

import numpy as np
from scipy.sparse.csgraph import maximum_flow

def reduce_to_max_flow(a):
    i, j = a.shape
    n = a.nnz
    data = np.concatenate([2*np.ones(i, dtype=int), np.ones(n + j, dtype=int)])
    indices = np.concatenate([np.arange(1, i + 1),
                              a.indices + i + 1,
                              np.repeat(i + j + 1, j)])
    indptr = np.concatenate([[0],
                             a.indptr + i,
                             np.arange(n + i + 1, n + i + j + 1),
                             [n + i + j]])
    graph = csr_matrix((data, indices, indptr), shape=(2+i+j, 2+i+j))
    flow = maximum_flow(graph, 0, graph.shape[0]-1)
    return flow.flow_value == 2*i

让我们在由 40 架飞机和 100 名飞行员组成的单个示例上比较两种方法的时序,该图的边密度为 0.1:

from scipy.sparse import random
inp = random(40, 100, density=.1, format='csr', dtype=bool)
%timeit reduce_to_max_matching(inp)  # 191 µs ± 3.57 µs per loop
%timeit reduce_to_max_flow(inp)      # 1.29 ms ± 20.1 µs per loop

基于匹配的方法速度更快,但速度并不快。在更大的问题上,我们将开始看到使用匹配的优势;拥有 400 架飞机和 1000 名飞行员:

inp = random(400, 1000, density=.1, format='csr', dtype=bool)
%timeit reduce_to_max_matching(inp)  # 473 µs ± 5.52 µs per loop
%timeit reduce_to_max_flow(inp)      # 68.9 ms ± 555 µs per loop

同样,这种精确的比较依赖于 SciPy 中特定预定义求解器的使用以及它们是如何实现的,但如果没有别的,这表明越简单越好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 2018-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多