数学建模实战:基于LNS与VRPTW的疫情物资配送优化方案

发布时间:2026/8/23 20:01:41
数学建模实战:基于LNS与VRPTW的疫情物资配送优化方案 1. 项目概述与核心问题拆解“COVID-19疫情期间生活物资的科学管理问题”是2022年中国研究生数学建模竞赛的F题。这个题目一出来当时就在我们建模圈子里引起了不小的讨论因为它太“接地气”了。它不像一些纯理论优化题那样飘在空中而是把一个我们每个人都亲身经历、甚至为之焦虑过的现实难题抽象成了一个严谨的数学问题。简单说题目核心就是当一个城市因为疫情封控居民无法自由外出采购时如何通过一套科学的管理和配送体系把蔬菜、粮油、药品这些救命的生活物资高效、公平、安全地送到每家每户这背后远不止是“怎么送快递”那么简单。它涉及到在极端压力场景下人力紧缺、道路管制、需求爆发且不确定的多目标动态优化。你需要考虑仓库物资集散点的选址让配送总距离最短你要规划配送车辆的路径在有限的车次和载重下尽可能多、快、好地完成任务你还要处理需求的预测与分配因为不同社区、不同家庭的需求紧迫度比如有慢性病患者的家庭对药品的需求和物资消耗速度是不同的。更深一层你还要权衡效率与公平是优先满足距离近的大社区还是确保偏远散户也能及时获得基本保障在运力极度紧张时如何制定物资分配的优先级规则这些都是题目隐含的挑战。这道题非常适合有一定数学建模基础特别是对优化算法、数据分析感兴趣的同学来深入研究。它不仅能锻炼你从复杂现实问题中提炼数学模型的能力更能让你体会到数学工具在解决重大社会实际问题中的强大力量。接下来我将以当年参赛者的视角为你完整拆解这道题的解题思路、核心模型构建、算法实现细节以及那些在论文里不会写的实操心得与避坑指南。2. 整体建模思路与核心模型选择面对这样一个复杂系统直接上手编程是行不通的。我们必须先搭建一个清晰的逻辑框架把现实问题“翻译”成数学语言。我的整体思路是分层优化分步求解。2.1 问题分解与模型架构首先我把整个物资管理问题分解为三个核心子问题它们之间存在逻辑上的先后与耦合关系需求点聚类与配送中心选址这是第一层优化。城市里有成千上万个居民点小区直接用车辆一一配送效率极低。合理的做法是先将地理位置上邻近的小区聚类成若干个“配送片区”然后在每个片区内选择一个最优位置作为临时配送中心或物资集散点。这样大宗物资先运送到这些配送中心再进行最后的末端配送。这本质上是一个设施选址问题目标是最小化所有居民点到其所属配送中心的距离总和或加权距离。车辆路径规划这是第二层也是核心的优化层。在每个配送中心我们需要为车队规划具体的送货路线。车辆从中心出发服务该片区内的若干个小区然后返回中心。这需要满足一系列约束车辆载重容量、每辆车最大行驶距离对应工作时间或油耗、每个小区的需求量、服务的时间窗要求例如生鲜食品需在上午送达。这就是经典的带容量和时间窗的车辆路径问题。动态需求预测与库存分配这是第三层用于处理不确定性。疫情下的物资需求不是一成不变的它会随着封控时间、社区疫情严重程度、家庭构成等因素动态变化。我们需要一个简单的预测模型如基于历史消耗率的指数平滑模型来预估未来几天的需求量并据此调整配送中心的库存水平和配送计划。同时在物资总量不足时需要建立优先级分配模型例如优先保障封控楼栋、有老人幼儿的家庭、医疗物资需求等。这三个子问题构成了一个“选址-路径-库存”集成优化模型。在实际求解时我们通常采用“先聚类选址再路径规划最后用预测和分配策略进行反馈调整”的序贯求解策略。对于研究生竞赛而言能清晰地将这个框架建立起来并求解前两层就已经能拿到不错的分数了。2.2 核心模型与算法选型理由接下来我详细解释每个子问题具体选用什么模型和算法以及为什么这么选。对于子问题1聚类与选址模型选择K-means聚类结合P-中值模型。理由K-means是解决聚类问题最直观、最常用的无监督学习算法它能根据居民点的经纬度坐标快速将其划分为K个簇。但单纯的K-means的簇中心质心可能落在不合理的位置如公园、河流中央。因此我们需要引入P-中值模型。该模型的目标是从所有候选点通常是居民点本身或道路网络节点中选出P个作为配送中心使得所有需求点到其最近配送中心的加权距离之和最小。这里的“权重”可以是该小区的家庭户数或预估需求量。求解算法对于P-中值模型精确求解如整数规划在大规模问题上计算量过大。我们采用启发式算法如模拟退火算法或遗传算法。以模拟退火为例它通过模拟固体退火过程以一定的概率接受“劣解”从而有效跳出局部最优逐步逼近全局较优的选址方案。这在竞赛时间限制内是可行的。对于子问题2车辆路径规划模型选择带容量约束和时间窗的车辆路径问题。理由这是物流配送领域最经典的问题之一能最贴切地描述我们的场景。其数学模型包括0-1决策变量表示车辆k是否从点i行驶到点j、车辆载重约束、每个点只能被一辆车服务一次约束、流量平衡约束、时间窗约束等。求解算法VRPTW是NP-hard问题精确求解同样不现实。我们采用改进的 Clarke-Wright 节约算法进行初始解构造然后使用大规模邻域搜索算法进行优化。LNS的思想是在每次迭代中通过“破坏”算子移除当前解中的一部分客户点如随机移除、移除距离最远的点然后使用“修复”算子如贪婪插入法、 regret-k 插入法将这些点重新插入到路径中寻找更好的解。LNS在求解质量和计算效率上取得了很好的平衡。对于子问题3需求预测与分配模型选择时间序列预测如Holt-Winters模型结合多准则决策分析如AHP层次分析法。理由对于短期需求预测Holt-Winters模型能同时捕捉数据的趋势性、季节性和水平适合物资消耗这类有一定规律的时间序列。对于优先级分配AHP方法可以将“紧急程度”、“人口结构”、“疫情风险”等难以量化的准则通过两两比较转化为权重从而为每个社区或家庭计算出一个综合优先级分数作为分配稀缺物资的依据。注意在竞赛中由于时间和数据限制第三部分往往不需要实现得极其复杂。一个合理的、有依据的简化模型配合清晰的论述比一个复杂但漏洞百出的模型得分更高。3. 模型构建与求解的详细步骤有了思路和选型我们进入实操环节。这里我以最核心的VRPTW问题为例展示从模型建立到代码求解的完整过程。3.1 数据准备与预处理假设我们通过聚类选址已经确定了一个配送中心及其需要服务的N个小区。我们拥有以下数据配送中心坐标 (depot_x, depot_y)每个小区i的坐标 (x_i, y_i)、需求量 demand_i、服务时间 service_i、时间窗 [e_i, l_i]最早开始服务时间和最晚开始服务时间车辆信息车辆数量K每辆车载重 capacity车辆行驶速度 speed用于计算行驶时间预处理关键步骤计算距离矩阵使用欧几里得距离或更真实的道路网络距离如果数据支持。dist_matrix[i][j]表示从点i到点j的距离。计算时间矩阵time_matrix[i][j] dist_matrix[i][j] / speed。时间窗可行性快速检查对于任意两点i和j如果time_i service_i time_matrix[i][j] l_j那么从i直接服务j在时间上就是不可行的。这个检查可以在算法中用于剪枝大幅减少搜索空间。3.2 VRPTW数学模型建立定义决策变量x_{ijk}二进制变量若车辆k从客户i行驶到客户j则为1否则为0。s_{ik}车辆k开始服务客户i的时间。Q_{ik}车辆k在离开客户i时的载重量。目标函数最小化总行驶距离或总行驶时间。Minimize Z sum_{k1}^{K} sum_{i0}^{N} sum_{j0}^{N} dist_matrix[i][j] * x_{ijk}其中索引0代表配送中心。约束条件每个客户只被服务一次sum_{k1}^{K} sum_{j0}^{N} x_{ijk} 1, for all i in customers车辆从中心出发并返回sum_{j1}^{N} x_{0jk} 1且sum_{i1}^{N} x_{i0k} 1, for all k流量平衡sum_{i0}^{N} x_{ihk} - sum_{j0}^{N} x_{hjk} 0, for all h in customers, for all k载重约束Q_{jk} Q_{ik} - demand_j * x_{ijk} capacity * (1 - x_{ijk})消除子回路并保证载重时间窗约束s_{ik} service_i time_matrix[i][j] - s_{jk} M * (1 - x_{ijk})大M法线性化服务时间约束e_i s_{ik} l_i这个模型完整定义了问题但直接求解非常困难。下面我们转向启发式算法。3.3 大规模邻域搜索算法实现详解我们使用Python来实现LNS算法。这里给出核心代码框架和关键操作的解释。import numpy as np import random import copy class VRPTW_Solver: def __init__(self, depot, customers, vehicle_capacity, num_vehicles): self.depot depot self.customers customers # list of dict with id, x, y, demand, e, l, service self.vehicle_capacity vehicle_capacity self.num_vehicles num_vehicles self.dist_matrix self._compute_distance_matrix() self.best_solution None self.best_cost float(inf) def _compute_distance_matrix(self): # 计算所有点中心客户之间的距离矩阵 points [self.depot] self.customers n len(points) dist np.zeros((n, n)) for i in range(n): for j in range(n): if i ! j: dist[i][j] np.sqrt((points[i][x]-points[j][x])**2 (points[i][y]-points[j][y])**2) return dist def _initial_solution(self): # 使用节约算法构造初始解 # 1. 为每个客户创建一条独立路线中心 - 客户 - 中心 routes [] for cust in self.customers: if cust[demand] self.vehicle_capacity: route {customers: [cust[id]], load: cust[demand], time: self._calculate_route_time([cust[id]])} routes.append(route) else: # 如果单个客户需求就超载说明问题无解或需要特殊处理 print(fCustomer {cust[id]} demand exceeds vehicle capacity!) return None # 2. 计算所有可能的路线合并所能节约的距离 savings [] for i in range(len(routes)): for j in range(i1, len(routes)): # 计算将路线i的末端和路线j的首端连接起来所节约的距离 # 节约值 dist(0,i_end) dist(0,j_start) - dist(i_end, j_start) saving self.dist_matrix[0][routes[i][customers][-1]] \ self.dist_matrix[0][routes[j][customers][0]] - \ self.dist_matrix[routes[i][customers][-1]][routes[j][customers][0]] savings.append((saving, i, j)) # 3. 按节约值从大到小排序尝试合并路线 savings.sort(reverseTrue, keylambda x: x[0]) for saving, i, j in savings: if len(routes) self.num_vehicles: break # 检查合并后的路线是否满足载重和时间窗约束 merged_customers routes[i][customers] routes[j][customers] merged_load routes[i][load] routes[j][load] if merged_load self.vehicle_capacity and self._check_time_window(merged_customers): # 执行合并 routes[i][customers] merged_customers routes[i][load] merged_load routes[i][time] self._calculate_route_time(merged_customers) del routes[j] # 需要重新计算涉及被删除路线j的节约值这里简化处理 return routes def _lns_search(self, current_solution, iterations1000): # 大规模邻域搜索主循环 for iter in range(iterations): # 1. 破坏随机移除一定比例如30%的客户 destroyed_solution, removed_customers self._destroy(current_solution, removal_rate0.3) # 2. 修复使用贪婪插入或 regret-2 插入法将移除的客户重新插入 new_solution self._repair(destroyed_solution, removed_customers) # 3. 接受准则模拟退火接受准则 new_cost self._calculate_total_cost(new_solution) current_cost self._calculate_total_cost(current_solution) delta new_cost - current_cost if delta 0 or random.random() np.exp(-delta / self._temperature(iter, iterations)): current_solution new_solution if new_cost self.best_cost: self.best_solution copy.deepcopy(new_solution) self.best_cost new_cost return self.best_solution def _destroy(self, solution, removal_rate): # 随机移除策略 destroyed copy.deepcopy(solution) all_customers [] for route in destroyed: all_customers.extend(route[customers]) num_to_remove int(len(all_customers) * removal_rate) removed random.sample(all_customers, num_to_remove) # 从路径中删除这些客户 new_routes [] for route in destroyed: new_route_custs [c for c in route[customers] if c not in removed] if new_route_custs: new_load sum(self.customers[c-1][demand] for c in new_route_custs) new_routes.append({customers: new_route_custs, load: new_load}) return new_routes, removed def _repair(self, partial_solution, removed_customers): # 使用 regret-2 插入法 # regret-k值衡量的是如果不把客户插入当前最好的位置而插入第k好的位置成本会增加多少 unserved list(removed_customers) while unserved: best_customer None best_route_idx None best_position None max_regret -float(inf) for cust_id in unserved: customer self.customers[cust_id-1] regret, (route_idx, pos) self._calculate_regret_k(partial_solution, cust_id, k2) if regret max_regret: max_regret regret best_customer cust_id best_route_idx route_idx best_position pos # 执行插入 if best_route_idx is not None: partial_solution[best_route_idx][customers].insert(best_position, best_customer) partial_solution[best_route_idx][load] self.customers[best_customer-1][demand] unserved.remove(best_customer) else: # 如果无法插入任何现有路径则开启一条新车路径需检查车辆数限制 if len(partial_solution) self.num_vehicles: new_route {customers: [best_customer], load: self.customers[best_customer-1][demand]} partial_solution.append(new_route) unserved.remove(best_customer) else: # 车辆数不足这是一个不可行解可以赋予一个很高的惩罚成本 break return partial_solution def solve(self): init_sol self._initial_solution() if init_sol: final_sol self._lns_search(init_sol, iterations500) return final_sol, self.best_cost else: return None, float(inf)代码关键点解析初始解构造节约算法是经典且有效的构造方法。它通过计算合并两条路线所能“节约”的距离优先合并节约值大的路线从而快速得到一个可行的初始解。破坏算子这里采用了最简单的随机移除。在实际比赛中可以尝试更复杂的策略如最差移除移除对当前路径成本贡献最大的客户、相关移除移除地理位置接近的客户组这些策略能引导搜索走向不同的区域。修复算子Regret-k插入法比简单的贪婪插入效果更好。贪婪插入只考虑当前最优容易陷入局部最优。Regret-k会前瞻性地考虑如果现在不把客户A插入其最优位置等会再插的话成本会多增加多少即后悔值。优先插入后悔值最大的客户能在全局上得到更好的解。接受准则采用了模拟退火的Metropolis准则。即使新解比当前解差也有一定概率接受这有助于算法跳出局部最优。温度函数_temperature(iter, iterations)会随着迭代进行而下降后期接受差解的概率变小搜索趋于稳定。约束检查在_check_time_window和插入操作中必须实时检查载重和时间窗约束确保解的可行性。这是算法正确性的基础。4. 模型拓展、灵敏度分析与论文写作要点一个完整的数模论文不仅要有模型和算法还需要展示你对问题的深入思考。4.1 模型拓展与多目标权衡原问题主要优化“总距离”这个单一目标。但在现实中管理者可能关心多个目标目标1总配送成本最小与距离强相关。目标2配送公平性最大如最小化所有客户等待时间的方差。目标3系统鲁棒性最强如对某个配送中心临时关闭的容忍度。这时问题就变成了一个多目标优化问题。我们可以采用加权和法或帕累托前沿求解法。加权和法给每个目标赋予一个权重将多目标转化为单目标。例如Minimize w1 * 总距离 w2 * 等待时间方差。关键在于权重的设定可以通过专家打分法AHP来确定。帕累托前沿使用多目标进化算法如NSGA-II来求解。它会得到一组“非支配解”这些解在多个目标之间无法相互比较谁更好即一个目标上的改进必然导致另一个目标上的恶化。决策者可以在这组解中根据偏好进行选择。在论文中画出帕累托前沿的示意图是体现模型深度的加分项。4.2 灵敏度分析灵敏度分析是检验模型可靠性和鲁棒性的关键环节。你需要回答“如果某个参数变了结果会怎样”。需求波动分析假设某个小区的需求量突然增加20%你的配送方案需要做出多大调整总成本会增加多少通过多次随机扰动需求量观察目标函数的变化范围。车辆数量分析如果可用的配送车辆减少1辆总配送距离和完成所有任务所需的时间会如何变化这能帮助管理者评估资源投入的边际效益。时间窗严苛度分析如果将所有客户的服务时间窗缩短要求配送更准时方案的可行性如何有多少客户可能无法被服务这能评估服务承诺的合理性。关键参数分析在聚类选址的P-中值模型中配送中心的数量P是一个关键参数。分析P从3变化到10时总加权距离的变化曲线。你会发现存在一个“拐点”超过这个点后增加配送中心带来的效益提升就非常有限了。这个拐点对应的P值就是经济上最合理的配送中心数量。4.3 论文写作与可视化呈现心得论文是最终成果的载体写得好才能拿高分。摘要这是重中之重要用精炼的语言在有限字数内说明“针对什么问题、建立了什么模型、采用了什么方法、得到了什么结果、有何结论与建议”。务必突出模型的亮点如集成优化、LNS算法、多目标权衡和关键结论如最优配送中心数量、成本节约比例。模型假设要合理且明确。例如“假设各小区的地理位置和需求已知”、“假设车辆匀速行驶且不考虑交通拥堵”、“假设每个客户必须在规定时间窗内被服务”。好的假设能简化问题同时让评委看到你对问题边界的把握。符号说明制作一个清晰的三线表列出所有模型中使用的主要变量、符号及其含义。这是专业性的体现。模型检验不要只给出结果要检验它。用小规模算例测试你的算法手动计算一个只有4-5个客户的问题的最优解然后看你的算法能否找到相同或接近的解。这能证明算法逻辑的正确性。可视化一图胜千言。聚类与选址图在地图上用不同颜色标注聚类结果并用星号标出选定的配送中心。车辆路径图为每辆车的路径用不同颜色的线条画出形成清晰的配送网络图。灵敏度分析图用折线图展示关键参数变化对目标的影响。结果对比图如果你的模型有改进比如对比简单的最近邻插入法用柱状图对比两种方法的总成本、车辆使用数等指标。优缺点与推广客观评价自己模型的优点如贴近实际、求解高效和缺点如未考虑动态交通、假设需求确定已知。并提出模型可以推广应用到其他场景如“双十一”物流配送、应急物资调度、共享单车调度等。5. 常见问题、调试技巧与参赛建议最后分享一些实战中踩过的坑和总结的经验。5.1 算法调试与性能提升初始解质量太差如果节约算法构造的初始解就很差LNS可能也无力回天。可以尝试多种初始解构造方法如最近邻法、插入法并选择最好的一个作为LNS的起点。算法陷入局部最优这是启发式算法的通病。除了调整模拟退火的初始温度和降温速率还可以增加扰动在LNS的破坏阶段提高移除客户的比例如从30%提高到50%给予算法更大的搜索空间。重启机制当连续多次迭代没有改进时保留历史最优解然后从当前解的一个随机扰动状态重新开始搜索。并行化尝试用多线程同时运行多个LNS进程每个进程使用不同的随机种子最后取最优结果。计算时间过长对于大规模问题客户点200纯Python实现的LNS可能会比较慢。优化方法关键函数向量化使用NumPy对距离计算、成本计算进行向量化操作避免低效的for循环。使用更高效的数据结构比如用array代替list存储路径信息。设定迭代上限或时间上限在竞赛中不必追求绝对最优在合理时间内得到一个满意解即可。5.2 模型与数据的自洽性检查量纲一致性这是最易出错的地方距离单位是公里还是米速度单位是公里/小时还是米/秒时间窗单位是小时还是分钟务必在整个模型中统一量纲否则时间窗约束会完全失效。约束冲突导致无解如果车辆载重太小或者客户时间窗过于严格可能导致问题无可行解。你的程序应该能检测到这种情况并给出提示如“无法在给定约束下服务所有客户建议增加车辆或放宽时间窗”而不是崩溃或陷入死循环。数据验证对生成的结果进行人工抽查。随机选一条路径手动加总一下该路径上所有客户的需求量看是否超过车辆载重。计算一下第一个客户和最后一个客户的到达时间看是否满足时间窗。这种简单的检查能避免低级错误。5.3 给参赛者的几点建议合理分工定期同步三人队伍理想分工是一人主攻模型建立与算法设计编程能力强一人主攻论文写作与图表绘制逻辑清晰、文笔好一人负责资料搜集、数据处理和模型检验细心严谨。每天至少开两次短会同步进度和问题。先完成再完美在三天或四天的竞赛中时间极其宝贵。不要一开始就追求最复杂、最前沿的模型。先用一个相对简单但完整的模型把问题解出来得到一套基础的结果和论文框架。后续再有时间再去增加模型的复杂度如引入多目标、动态需求。重视可视化与表述评委评阅每篇论文的时间有限。清晰美观的图表、逻辑严谨的表述、重点突出的摘要能让你在众多论文中脱颖而出。即使模型稍逊优秀的呈现也能挽回不少分数。代码与文档备份使用Git进行版本管理或者每小时手动备份一次。避免因电脑故障导致前功尽弃。最后提交前确保提供的代码能一键运行并附上详细的README说明。保持体力与心态数模竞赛是脑力也是体力的马拉松。准备一些提神的饮料和食物合理安排休息。遇到卡壳时及时与队友讨论或暂时切换任务保持积极的心态至关重要。这道F题是一个绝佳的练手项目它涵盖了从实际问题抽象、数学模型构建、算法设计实现到结果分析呈现的全流程。希望这份超详细的思路拆解和实战指南能帮助你不仅理解这道题更能掌握解决一类复杂优化问题的方法论。真正的价值不在于复现这段代码而在于学会这种“分解-建模-求解-分析”的思维模式这才是数学建模竞赛带给你的最宝贵的财富。