- 从数学角度来看,线性分配问题(也称为匈牙利算法或指派问题)是一个经典的优化问题,其目的是在两个集合之间找到最佳匹配,使得总成本最小。我们可以将其形式化为一个二分图的最小权匹配问题。
数学背景
假设我们有两个集合 ( A ) 和 ( B ),其中 ( A ) 有 ( m ) 个元素,( B ) 有 ( n ) 个元素。我们有一个 ( m \times n ) 的成本矩阵 ( C ),其中 ( C[i, j] ) 表示将 ( A ) 中的第 ( i ) 个元素分配给 ( B ) 中的第 ( j ) 个元素的成本。
目标是找到一个匹配 ( M ⊆ A × B ) (M \subseteq A \times B ) (M⊆A×B),使得总成本
∑ ( i , j ) ∈ M C [ i , j ] \sum_{(i, j) \in M} C[i, j] ∑(i,j)∈MC[i,j]
最小,并且每个元素最多匹配一次。
算法解释
1. 空成本矩阵的处理
如果成本矩阵为空(即没有元素),则直接返回空匹配和未匹配的索引。
if cost_matrix.size == 0:
return np.empty((0, 2), dtype=int), tuple(range(cost_matrix.shape[0])), tuple(range(cost_matrix.shape[1]))
2. 使用 LAPJV 算法
如果选择使用 LAPJV 算法(来自 lap
库),则执行以下步骤:
_, x, y = lap.lapjv(cost_matrix, extend_cost=True, cost_limit=thresh)
lap.lapjv
函数返回三个数组:- 第一个数组(未使用)是总成本。
x
数组表示每个 ( A ) 中元素的匹配结果,如果未匹配则为 -1。y
数组表示每个 ( B ) 中元素的匹配结果,如果未匹配则为 -1。
matches = [[ix, mx] for ix, mx in enumerate(x) if mx >= 0]
unmatched_a = np.where(x < 0)[0]
unmatched_b = np.where(y < 0)[0]
matches
是一个列表,包含所有有效的匹配对。unmatched_a
和unmatched_b
分别是未匹配的 ( A ) 和 ( B ) 中的索引。
3. 使用 SciPy 的线性和分配算法
如果选择使用 SciPy 的 linear_sum_assignment
算法,则执行以下步骤:
x, y = scipy.optimize.linear_sum_assignment(cost_matrix)
scipy.optimize.linear_sum_assignment
返回两个数组:x
表示 ( A ) 中元素的匹配索引。y
表示 ( B ) 中元素的匹配索引。
matches = np.asarray([[x[i], y[i]] for i in range(len(x)) if cost_matrix[x[i], y[i]] <= thresh])
- 这行代码筛选出所有匹配成本不超过阈值的匹配对。
if len(matches) == 0:
unmatched_a = list(np.arange(cost_matrix.shape[0]))
unmatched_b = list(np.arange(cost_matrix.shape[1]))
else:
unmatched_a = list(set(np.arange(cost_matrix.shape[0])) - set(matches[:, 0]))
unmatched_b = list(set(np.arange(cost_matrix.shape[1])) -