用PythonPuLP库5分钟搞定匈牙利算法指派问题当你在数学建模比赛中遇到任务分配问题或是作为项目经理需要优化团队工作分配时手动计算匈牙利算法可能会让你抓狂。那些繁琐的矩阵变换、试指派步骤不仅耗时还容易出错。幸运的是Python的PuLP库能让我们在几分钟内解决这类问题把精力集中在更有价值的决策分析上。1. 为什么选择代码实现匈牙利算法匈牙利算法作为解决指派问题的经典方法在运筹学领域已有数十年历史。传统的手工计算方式需要经过以下步骤构建代价矩阵行归约和列归约试指派寻找独立零元素画线覆盖所有零元素矩阵调整这些步骤不仅重复性高而且在处理大规模问题时比如20×20以上的矩阵人工计算几乎不可能完成。我曾在一个物流优化项目中遇到需要分配30辆卡车到30个送货点的场景手工计算花费了整整两小时而使用Python代码只需不到5秒。代码实现的三大优势准确性避免人工计算中的疏忽和错误效率处理大规模问题时速度是指数级提升可复用性一次编写多次使用适应不同场景2. PuLP库简介与安装PuLP是Python中用于线性规划的强大库它提供了直观的API来描述优化问题。与SciPy等其他优化库相比PuLP的语法更接近数学表达特别适合运筹学问题的建模。安装PuLP非常简单只需运行pip install pulpPuLP支持多种求解器包括CBC默认开源GLPK开源CPLEX商业Gurobi商业对于大多数指派问题默认的CBC求解器已经足够高效。下面是一个简单的PuLP问题建模示例import pulp # 创建问题实例 prob pulp.LpProblem(Simple_Problem, pulp.LpMinimize) # 定义变量 x pulp.LpVariable(x, lowBound0) y pulp.LpVariable(y, lowBound0) # 添加目标函数 prob 3*x 5*y # 添加约束条件 prob 2*x 3*y 10 prob x - y 1 # 求解 prob.solve() # 输出结果 print(Status:, pulp.LpStatus[prob.status]) print(x , pulp.value(x)) print(y , pulp.value(y))3. 使用PuLP实现匈牙利算法让我们通过一个具体的任务分配问题来演示如何使用PuLP解决指派问题。假设有4个员工和4项任务每个员工完成各项任务的时间成本如下表员工\任务任务A任务B任务C任务D员工138210员工28729员工36427员工48423我们的目标是将每项任务分配给一个员工每个员工只负责一项任务使总耗时最少。import pulp # 创建问题实例 prob pulp.LpProblem(Assignment_Problem, pulp.LpMinimize) # 定义代价矩阵 cost_matrix [ [3, 8, 2, 10], [8, 7, 2, 9], [6, 4, 2, 7], [8, 4, 2, 3] ] # 获取员工和任务的数量 num_workers len(cost_matrix) num_tasks len(cost_matrix[0]) # 创建决策变量 x pulp.LpVariable.dicts(assign, ((i, j) for i in range(num_workers) for j in range(num_tasks)), catBinary) # 添加目标函数 prob pulp.lpSum(cost_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks)) # 添加约束条件每个员工只能分配一个任务 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 1 # 添加约束条件每个任务只能分配给一个员工 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 求解问题 prob.solve() # 输出结果 print(最优分配方案) for i in range(num_workers): for j in range(num_tasks): if pulp.value(x[(i, j)]) 1: print(f员工{i1} - 任务{j1} (耗时: {cost_matrix[i][j]}小时)) print(f总最短耗时: {pulp.value(prob.objective)}小时)这段代码会输出最优的分配方案和最小总耗时。在我的测试中对于4×4的问题求解时间通常在0.1秒以内。4. 处理非标准指派问题现实中的指派问题往往不是完美的n×n形式PuLP同样能灵活处理这些情况。4.1 人数与任务数不等当员工数量多于任务数量时我们需要修改约束条件# 假设有5个员工4项任务 num_workers 5 num_tasks 4 # ...其余部分与前面相同 # 修改约束每个任务只能分配给一个员工不变 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 修改约束每个员工最多分配一个任务不是必须分配 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 14.2 最大化问题有些场景下我们需要最大化收益而非最小化成本只需将目标函数改为最大化# 创建最大化问题实例 prob pulp.LpProblem(Maximize_Profit, pulp.LpMaximize) # 定义收益矩阵 profit_matrix [ [10, 5, 8, 12], [7, 8, 6, 9], [9, 6, 7, 8], [8, 7, 9, 10] ] # 修改目标函数 prob pulp.lpSum(profit_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks))4.3 特殊约束处理有时我们需要添加特殊约束比如员工3不能做任务A# 禁止员工3做任务A prob x[(2, 0)] 0 # 注意Python是0-based索引或者员工1必须做任务B或C# 员工1必须做任务B或C prob x[(0, 1)] x[(0, 2)] 15. 性能优化与实用技巧虽然PuLP已经非常高效但在处理大规模指派问题时如100×100以上我们可以采用一些优化策略使用更高效的求解器商业求解器如Gurobi或CPLEX可以显著提升速度问题分解对于超大规模问题考虑分解为多个子问题并行计算利用多核CPU并行处理一个实用的调试技巧是在求解前检查模型# 输出问题模型供检查 print(prob)对于需要频繁解决类似问题的场景可以将代码封装为函数def solve_assignment(cost_matrix, is_maximizationFalse, constraintsNone): 解决指派问题的通用函数 参数: cost_matrix: 代价/收益矩阵 is_maximization: 是否为最大化问题 constraints: 特殊约束列表 返回: 分配方案和总成本/收益 # 创建问题实例 prob_type pulp.LpMaximize if is_maximization else pulp.LpMinimize prob pulp.LpProblem(Assignment, prob_type) # 定义变量和基本约束 num_workers len(cost_matrix) num_tasks len(cost_matrix[0]) if num_workers 0 else 0 x pulp.LpVariable.dicts(assign, ((i, j) for i in range(num_workers) for j in range(num_tasks)), catBinary) # 目标函数 prob pulp.lpSum(cost_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks)) # 基本约束 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 1 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 添加特殊约束 if constraints: for constraint in constraints: prob constraint # 求解 prob.solve() # 收集结果 assignment [] total_cost 0 for i in range(num_workers): for j in range(num_tasks): if pulp.value(x[(i, j)]) 1: assignment.append((i, j)) total_cost cost_matrix[i][j] return assignment, total_cost这个封装函数可以处理大多数指派问题场景使用时只需# 示例使用 cost_matrix [ [3, 8, 2, 10], [8, 7, 2, 9], [6, 4, 2, 7], [8, 4, 2, 3] ] # 添加特殊约束员工3不能做任务A (索引从0开始) constraints [ pulp.lpSum([x[(2, 0)]]) 0 ] assignment, total_cost solve_assignment(cost_matrix, constraintsconstraints)在实际项目中我发现将结果可视化能极大提升方案的可理解性。可以使用matplotlib绘制甘特图或分配矩阵import matplotlib.pyplot as plt import numpy as np def plot_assignment(cost_matrix, assignment): 可视化分配结果 n len(cost_matrix) fig, ax plt.subplots(figsize(8, 8)) # 创建矩阵图像 img np.zeros((n, n)) for i, j in assignment: img[i, j] 1 ax.imshow(img, cmapBlues) # 添加标签 ax.set_xticks(np.arange(n)) ax.set_yticks(np.arange(n)) ax.set_xticklabels([f任务{i1} for i in range(n)]) ax.set_yticklabels([f员工{i1} for i in range(n)]) # 在每个方格显示成本 for i in range(n): for j in range(n): ax.text(j, i, cost_matrix[i][j], hacenter, vacenter, colorblack) plt.title(最优任务分配方案) plt.tight_layout() plt.show() # 使用示例 plot_assignment(cost_matrix, assignment)对于需要频繁运行的生产环境可以考虑将解决方案部署为REST API使用FastAPI等框架from fastapi import FastAPI import pulp from pydantic import BaseModel app FastAPI() class AssignmentRequest(BaseModel): cost_matrix: list[list[float]] constraints: list[str] None is_maximization: bool False app.post(/solve-assignment) def solve_assignment_api(request: AssignmentRequest): # 这里调用前面的solve_assignment函数 assignment, total_cost solve_assignment( request.cost_matrix, request.is_maximization, request.constraints ) return { assignment: assignment, total_cost: total_cost, status: optimal }这样其他系统可以通过HTTP请求轻松集成指派问题求解功能。
别再死记硬背了!用Python+PuLP库5分钟搞定匈牙利算法指派问题
发布时间:2026/5/30 12:15:25
用PythonPuLP库5分钟搞定匈牙利算法指派问题当你在数学建模比赛中遇到任务分配问题或是作为项目经理需要优化团队工作分配时手动计算匈牙利算法可能会让你抓狂。那些繁琐的矩阵变换、试指派步骤不仅耗时还容易出错。幸运的是Python的PuLP库能让我们在几分钟内解决这类问题把精力集中在更有价值的决策分析上。1. 为什么选择代码实现匈牙利算法匈牙利算法作为解决指派问题的经典方法在运筹学领域已有数十年历史。传统的手工计算方式需要经过以下步骤构建代价矩阵行归约和列归约试指派寻找独立零元素画线覆盖所有零元素矩阵调整这些步骤不仅重复性高而且在处理大规模问题时比如20×20以上的矩阵人工计算几乎不可能完成。我曾在一个物流优化项目中遇到需要分配30辆卡车到30个送货点的场景手工计算花费了整整两小时而使用Python代码只需不到5秒。代码实现的三大优势准确性避免人工计算中的疏忽和错误效率处理大规模问题时速度是指数级提升可复用性一次编写多次使用适应不同场景2. PuLP库简介与安装PuLP是Python中用于线性规划的强大库它提供了直观的API来描述优化问题。与SciPy等其他优化库相比PuLP的语法更接近数学表达特别适合运筹学问题的建模。安装PuLP非常简单只需运行pip install pulpPuLP支持多种求解器包括CBC默认开源GLPK开源CPLEX商业Gurobi商业对于大多数指派问题默认的CBC求解器已经足够高效。下面是一个简单的PuLP问题建模示例import pulp # 创建问题实例 prob pulp.LpProblem(Simple_Problem, pulp.LpMinimize) # 定义变量 x pulp.LpVariable(x, lowBound0) y pulp.LpVariable(y, lowBound0) # 添加目标函数 prob 3*x 5*y # 添加约束条件 prob 2*x 3*y 10 prob x - y 1 # 求解 prob.solve() # 输出结果 print(Status:, pulp.LpStatus[prob.status]) print(x , pulp.value(x)) print(y , pulp.value(y))3. 使用PuLP实现匈牙利算法让我们通过一个具体的任务分配问题来演示如何使用PuLP解决指派问题。假设有4个员工和4项任务每个员工完成各项任务的时间成本如下表员工\任务任务A任务B任务C任务D员工138210员工28729员工36427员工48423我们的目标是将每项任务分配给一个员工每个员工只负责一项任务使总耗时最少。import pulp # 创建问题实例 prob pulp.LpProblem(Assignment_Problem, pulp.LpMinimize) # 定义代价矩阵 cost_matrix [ [3, 8, 2, 10], [8, 7, 2, 9], [6, 4, 2, 7], [8, 4, 2, 3] ] # 获取员工和任务的数量 num_workers len(cost_matrix) num_tasks len(cost_matrix[0]) # 创建决策变量 x pulp.LpVariable.dicts(assign, ((i, j) for i in range(num_workers) for j in range(num_tasks)), catBinary) # 添加目标函数 prob pulp.lpSum(cost_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks)) # 添加约束条件每个员工只能分配一个任务 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 1 # 添加约束条件每个任务只能分配给一个员工 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 求解问题 prob.solve() # 输出结果 print(最优分配方案) for i in range(num_workers): for j in range(num_tasks): if pulp.value(x[(i, j)]) 1: print(f员工{i1} - 任务{j1} (耗时: {cost_matrix[i][j]}小时)) print(f总最短耗时: {pulp.value(prob.objective)}小时)这段代码会输出最优的分配方案和最小总耗时。在我的测试中对于4×4的问题求解时间通常在0.1秒以内。4. 处理非标准指派问题现实中的指派问题往往不是完美的n×n形式PuLP同样能灵活处理这些情况。4.1 人数与任务数不等当员工数量多于任务数量时我们需要修改约束条件# 假设有5个员工4项任务 num_workers 5 num_tasks 4 # ...其余部分与前面相同 # 修改约束每个任务只能分配给一个员工不变 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 修改约束每个员工最多分配一个任务不是必须分配 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 14.2 最大化问题有些场景下我们需要最大化收益而非最小化成本只需将目标函数改为最大化# 创建最大化问题实例 prob pulp.LpProblem(Maximize_Profit, pulp.LpMaximize) # 定义收益矩阵 profit_matrix [ [10, 5, 8, 12], [7, 8, 6, 9], [9, 6, 7, 8], [8, 7, 9, 10] ] # 修改目标函数 prob pulp.lpSum(profit_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks))4.3 特殊约束处理有时我们需要添加特殊约束比如员工3不能做任务A# 禁止员工3做任务A prob x[(2, 0)] 0 # 注意Python是0-based索引或者员工1必须做任务B或C# 员工1必须做任务B或C prob x[(0, 1)] x[(0, 2)] 15. 性能优化与实用技巧虽然PuLP已经非常高效但在处理大规模指派问题时如100×100以上我们可以采用一些优化策略使用更高效的求解器商业求解器如Gurobi或CPLEX可以显著提升速度问题分解对于超大规模问题考虑分解为多个子问题并行计算利用多核CPU并行处理一个实用的调试技巧是在求解前检查模型# 输出问题模型供检查 print(prob)对于需要频繁解决类似问题的场景可以将代码封装为函数def solve_assignment(cost_matrix, is_maximizationFalse, constraintsNone): 解决指派问题的通用函数 参数: cost_matrix: 代价/收益矩阵 is_maximization: 是否为最大化问题 constraints: 特殊约束列表 返回: 分配方案和总成本/收益 # 创建问题实例 prob_type pulp.LpMaximize if is_maximization else pulp.LpMinimize prob pulp.LpProblem(Assignment, prob_type) # 定义变量和基本约束 num_workers len(cost_matrix) num_tasks len(cost_matrix[0]) if num_workers 0 else 0 x pulp.LpVariable.dicts(assign, ((i, j) for i in range(num_workers) for j in range(num_tasks)), catBinary) # 目标函数 prob pulp.lpSum(cost_matrix[i][j] * x[(i, j)] for i in range(num_workers) for j in range(num_tasks)) # 基本约束 for i in range(num_workers): prob pulp.lpSum(x[(i, j)] for j in range(num_tasks)) 1 for j in range(num_tasks): prob pulp.lpSum(x[(i, j)] for i in range(num_workers)) 1 # 添加特殊约束 if constraints: for constraint in constraints: prob constraint # 求解 prob.solve() # 收集结果 assignment [] total_cost 0 for i in range(num_workers): for j in range(num_tasks): if pulp.value(x[(i, j)]) 1: assignment.append((i, j)) total_cost cost_matrix[i][j] return assignment, total_cost这个封装函数可以处理大多数指派问题场景使用时只需# 示例使用 cost_matrix [ [3, 8, 2, 10], [8, 7, 2, 9], [6, 4, 2, 7], [8, 4, 2, 3] ] # 添加特殊约束员工3不能做任务A (索引从0开始) constraints [ pulp.lpSum([x[(2, 0)]]) 0 ] assignment, total_cost solve_assignment(cost_matrix, constraintsconstraints)在实际项目中我发现将结果可视化能极大提升方案的可理解性。可以使用matplotlib绘制甘特图或分配矩阵import matplotlib.pyplot as plt import numpy as np def plot_assignment(cost_matrix, assignment): 可视化分配结果 n len(cost_matrix) fig, ax plt.subplots(figsize(8, 8)) # 创建矩阵图像 img np.zeros((n, n)) for i, j in assignment: img[i, j] 1 ax.imshow(img, cmapBlues) # 添加标签 ax.set_xticks(np.arange(n)) ax.set_yticks(np.arange(n)) ax.set_xticklabels([f任务{i1} for i in range(n)]) ax.set_yticklabels([f员工{i1} for i in range(n)]) # 在每个方格显示成本 for i in range(n): for j in range(n): ax.text(j, i, cost_matrix[i][j], hacenter, vacenter, colorblack) plt.title(最优任务分配方案) plt.tight_layout() plt.show() # 使用示例 plot_assignment(cost_matrix, assignment)对于需要频繁运行的生产环境可以考虑将解决方案部署为REST API使用FastAPI等框架from fastapi import FastAPI import pulp from pydantic import BaseModel app FastAPI() class AssignmentRequest(BaseModel): cost_matrix: list[list[float]] constraints: list[str] None is_maximization: bool False app.post(/solve-assignment) def solve_assignment_api(request: AssignmentRequest): # 这里调用前面的solve_assignment函数 assignment, total_cost solve_assignment( request.cost_matrix, request.is_maximization, request.constraints ) return { assignment: assignment, total_cost: total_cost, status: optimal }这样其他系统可以通过HTTP请求轻松集成指派问题求解功能。