指派问题进阶:用 linear_sum_assignment 处理 5人7任务的不平衡分配
不平衡指派问题的实战解析:用linear_sum_assignment处理5人7任务分配
在现实世界的资源分配场景中,我们经常遇到"人多任务少"或"人少任务多"的不平衡情况。传统指派问题假设人数与任务数相等,但实际业务中这种理想情况很少见。本文将深入探讨如何使用Python的 scipy.optimize.linear_sum_assignment 函数解决这类非标准指派问题,特别是5人7任务这种典型的不平衡分配场景。
1. 指派问题基础与不平衡场景挑战
指派问题(Assignment Problem)是运筹学中的经典问题,目标是将n个任务分配给n个执行者(通常是人或机器),使得总成本最小或总效率最高。当人数与任务数相等时,我们可以直接构建方阵成本矩阵,使用匈牙利算法等传统方法求解。
不平衡指派问题的特殊性 在于:
- 人数≠任务数,成本矩阵变为矩形
- 需要明确分配策略:是让所有任务都被完成,还是所有人都有任务
- 可能存在"虚拟"实体来平衡矩阵维度
# 标准指派问题示例(3人3任务)
import numpy as np
from scipy.optimize import linear_sum_assignment
cost_matrix = np.array([[4, 1, 3],
[2, 0, 5],
[3, 2, 2]])
row_ind, col_ind = linear_sum_assignment(cost_matrix)
print(f"最优分配索引:{list(zip(row_ind, col_ind))}")
print(f"最小总成本:{cost_matrix[row_ind, col_ind].sum()}")
对于5人7任务的不平衡场景,直接使用上述方法会报错,因为 linear_sum_assignment 虽然支持矩形矩阵,但需要特定的处理策略。
2. 不平衡问题的矩阵转换策略
处理5人7任务的不平衡分配,核心思路是通过矩阵转换将矩形矩阵变为方阵。常用方法有:
2.1 虚拟任务填充法
当任务数多于人数时(如5人7任务),可以引入"虚拟人员"来平衡矩阵:
- 计算差额:7任务 - 5人 = 2,需要增加2个虚拟人员
- 构建7×7方阵,新增的2行填充特定值
- 虚拟人员的成本通常设为:
- 0:表示这些任务可以被"免费"完成(相当于允许某些人不被分配任务)
- 极大值:强制原始人员必须承担所有任务
# 原始5人7任务成本矩阵(示例)
original_cost = np.array([
[3, 8, 6, 9, 5, 9, 7],
[7, 5,12, 9,19, 1,23],
[7,10, 9,10, 6,89, 1],
[4, 6, 2,12, 8, 9,13],
[9, 9, 6,15,11, 7, 2]
])
# 添加2个虚拟人员(填充0)
padded_cost = np.vstack([
original_cost,
np.zeros((2, 7)) # 虚拟人员行
])
row_ind, col_ind = linear_sum_assignment(padded_cost)
2.2 任务拆分法
另一种思路是将多出的任务拆分为子任务,通过"人员分身"来平衡矩阵:
- 计算每人需要承担的平均任务数:ceil(7/5)=2
- 将每个原始人员复制为2个"分身",得到10人
- 保持原始7个任务,添加3个虚拟任务(成本设为0)
- 构建10×10方阵
# 人员分身并添加虚拟任务
expanded_cost = np.array([
[3,8,6,9,5,9,7,0,0,0], # 人员1的分身1
[3,8,6,9,5,9,7,0,0,0], # 人员1的分身2
[7,5,12,9,19,1,23,0,0,0],
[7,5,12,9,19,1,23,0,0,0],
[7,10,9,10,6,89,1,0,0,0],
[7,10,9,10,6,89,1,0,0,0],
[4,6,2,12,8,9,13,0,0,0],
[4,6,2,12,8,9,13,0,0,0],
[9,9,6,15,11,7,2,0,0,0],
[9,9,6,15,11,7,2,0,0,0]
])
row_ind, col_ind = linear_sum_assignment(expanded_cost)
3. 实战:5人7任务完整解决方案
让我们通过一个完整案例演示如何处理5人7任务分配。假设某IT公司有5名开发人员,需要完成7个不同模块的开发,各人员对模块的预估工时如下表:
| 人员\模块 | M1 | M2 | M3 | M4 | M5 | M6 | M7 |
|---|---|---|---|---|---|---|---|
| Dev1 | 10 | 15 | 12 | 8 | 20 | 18 | 9 |
| Dev2 | 12 | 8 | 10 | 15 | 25 | 12 | 6 |
| Dev3 | 9 | 11 | 14 | 10 | 15 | 20 | 12 |
| Dev4 | 14 | 13 | 9 | 12 | 18 | 15 | 10 |
| Dev5 | 11 | 10 | 13 | 9 | 22 | 14 | 8 |
3.1 方案实施步骤
步骤1:构建扩展成本矩阵
采用人员分身策略,每人拆分为2个虚拟人员(因为ceil(7/5)=2),并添加3个虚拟任务:
import numpy as np
from scipy.optimize import linear_sum_assignment
# 原始成本矩阵
cost = np.array([
[10,15,12,8,20,18,9],
[12,8,10,15,25,12,6],
[9,11,14,10,15,20,12],
[14,13,9,12,18,15,10],
[11,10,13,9,22,14,8]
])
# 人员分身并添加虚拟任务(成本设为0)
expanded_matrix = np.zeros((10,10))
for i in range(5):
expanded_matrix[2*i,:7] = cost[i]
expanded_matrix[2*i+1,:7] = cost[i]
# 虚拟任务列(8-10列)保持为0
步骤2:求解扩展问题
row_ind, col_ind = linear_sum_assignment(expanded_matrix)
total_cost = expanded_matrix[row_ind, col_ind].sum()
步骤3:结果解析与映射
将分身结果映射回原始人员:
assignment = {}
for dev_idx, task_idx in zip(row_ind, col_ind):
if task_idx < 7: # 忽略虚拟任务
original_dev = dev_idx // 2
if original_dev not in assignment:
assignment[original_dev] = []
assignment[original_dev].append(task_idx)
print("最优分配方案:")
for dev, tasks in assignment.items():
print(f"开发人员{dev+1} -> 模块{[t+1 for t in tasks]}")
print(f"预估总工时:{total_cost}")
3.2 方案优化与注意事项
在实际应用中,我们还需要考虑以下优化点:
-
虚拟任务成本设置 :
- 填0:允许某些任务不被分配
- 填极大值:强制分配所有原始任务
- 填平均值:平衡分配
-
分身策略调整 :
- 可以不等分分身,根据人员能力差异分配不同数量的任务
- 引入权重反映人员负载均衡
-
约束处理 :
- 添加人员能力限制
- 处理任务间的依赖关系
# 带权重的不平衡分配示例
weighted_matrix = expanded_matrix.copy()
for i in range(5):
weighted_matrix[2*i+1,:] *= 1.2 # 第二个分身成本增加20%,减少同一人员被分配过多任务
4. 不同不平衡场景的决策指南
根据人数与任务数的不同关系,应采取不同的处理策略:
| 场景类型 | 处理策略 | 矩阵转换方法 | 适用场景 |
|---|---|---|---|
| 人多任务少 | 虚拟任务法 | 添加虚拟任务列 | 部分人员可以闲置 |
| 人少任务多 | 人员分身法 | 复制人员行并添加虚拟任务 | 人员需要承担多个任务 |
| 复杂约束 | 混合整数规划 | 使用PuLP等库构建完整模型 | 有额外约束条件 |
| 动态变化 | 增量分配 | 滚动时域优化 | 任务或人员随时间变化 |
提示:对于超大规模不平衡问题(如1000人5000任务),建议采用分布式优化算法或启发式方法,而非直接扩展矩阵。
5. 高级应用与性能优化
当处理大规模或不平衡程度高的指派问题时,需要考虑算法性能和精度的平衡:
5.1 稀疏矩阵优化
对于多数元素相同(如虚拟任务对应的成本)的矩阵,使用稀疏矩阵表示可以大幅降低内存使用:
from scipy.sparse import csr_matrix
# 将扩展矩阵转换为稀疏格式
sparse_cost = csr_matrix(expanded_matrix)
5.2 并行计算
对于需要多次求解类似问题的场景(如参数调优),可以使用并行化:
from joblib import Parallel, delayed
def solve_assignment(cost_matrix):
return linear_sum_assignment(cost_matrix)
# 并行求解多个变种问题
results = Parallel(n_jobs=4)(
delayed(solve_assignment)(matrix_variant)
for matrix_variant in multiple_matrices
)
5.3 近似算法
当问题规模极大时,可以考虑近似算法:
def greedy_assignment(cost_matrix):
"""贪心算法近似求解"""
row_ind, col_ind = [], []
remaining_rows = set(range(cost_matrix.shape[0]))
remaining_cols = set(range(cost_matrix.shape[1]))
while remaining_rows and remaining_cols:
# 找到当前最小成本元素
min_cost = np.inf
best_pair = None
for i in remaining_rows:
for j in remaining_cols:
if cost_matrix[i,j] < min_cost:
min_cost = cost_matrix[i,j]
best_pair = (i,j)
row_ind.append(best_pair[0])
col_ind.append(best_pair[1])
remaining_rows.remove(best_pair[0])
remaining_cols.remove(best_pair[1])
return np.array(row_ind), np.array(col_ind)
在实际项目中,我曾处理过一个23人37任务的不平衡分配问题。通过组合使用人员分身和虚拟任务技术,将问题转化为46×46的方阵(23人×2=46,添加9个虚拟任务),最终在合理时间内获得了满意的分配方案。关键发现是虚拟任务的成本设置对结果影响显著——设置过高会导致分配过于集中,设置过低则可能使某些重要任务得不到足够资源。
更多推荐



所有评论(0)