不平衡指派问题的实战解析:用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任务),可以引入"虚拟人员"来平衡矩阵:

  1. 计算差额:7任务 - 5人 = 2,需要增加2个虚拟人员
  2. 构建7×7方阵,新增的2行填充特定值
  3. 虚拟人员的成本通常设为:
    • 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 任务拆分法

另一种思路是将多出的任务拆分为子任务,通过"人员分身"来平衡矩阵:

  1. 计算每人需要承担的平均任务数:ceil(7/5)=2
  2. 将每个原始人员复制为2个"分身",得到10人
  3. 保持原始7个任务,添加3个虚拟任务(成本设为0)
  4. 构建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 方案优化与注意事项

在实际应用中,我们还需要考虑以下优化点:

  1. 虚拟任务成本设置

    • 填0:允许某些任务不被分配
    • 填极大值:强制分配所有原始任务
    • 填平均值:平衡分配
  2. 分身策略调整

    • 可以不等分分身,根据人员能力差异分配不同数量的任务
    • 引入权重反映人员负载均衡
  3. 约束处理

    • 添加人员能力限制
    • 处理任务间的依赖关系
# 带权重的不平衡分配示例
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个虚拟任务),最终在合理时间内获得了满意的分配方案。关键发现是虚拟任务的成本设置对结果影响显著——设置过高会导致分配过于集中,设置过低则可能使某些重要任务得不到足够资源。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐