5G应急物资配送建模:改进遗传算法与通信约束耦合实战
简介本资源为2022年电工杯数学建模竞赛B题「5G网络环境下应急物资配送问题」的完整解题资料包面向参加数学建模竞赛的高校学生、需要课程设计或毕设选题的进阶学习者以及研究车辆与无人机协同配送路径优化的技术人员。资源围绕应急场景下的物资配送建模与求解展开涵盖问题分析、模型构建与算法实现等核心环节。压缩包共18个文件约272KB以11个Python脚本为主体包含蚁群算法、TSP求解及车辆路径规划等实现代码另附3个xlsx数据表、1份PDF赛题原文与1份README说明文档便于对照题目复现实验。目前已有209人学习下载。读者可从中获得完整的赛题求解思路、可运行的算法脚本、原始数据与模型配置适合作为建模入门参考或进一步改进算法的基础素材。1. 应急物资配送遇上5G这道题到底在考什么2022年电工杯B题把「5G网络环境下应急物资配送」摆上台面时很多人第一反应是套一个VRP车辆路径问题模型就完事。真动手才发现题目真正想考的不是单纯的路径优化而是当通信条件从「理想」变成「有带宽、有延迟、有中断风险」时配送决策该怎么跟着变。换句话说5G在这里不是背景板它是会改变约束条件的一个变量。这道题适合已经会写基础遗传算法或蚁群算法、但没处理过「通信约束耦合进优化模型」的读者。如果你正在做数学建模竞赛、应急调度仿真或者想找一个能把通信指标和运筹优化串起来的练手题这篇笔记会从建模思路一路讲到代码复现和参数调优把踩过的坑摊开说。2. 把5G约束翻译成数学语言从场景到模型的三步拆解2.1 先搞清楚题目里的「5G」到底约束了什么很多队伍一上来就写目标函数结果写到一半发现5G这个条件根本没用上。常见做法是先把题目描述里的通信相关表述逐条抽出来映射成可量化的约束。应急物资配送场景下5G网络能影响决策的点通常有三个一是车辆与调度中心之间的实时通信决定了路径能否动态调整二是物资需求点的状态上报延迟影响需求预测的时效性三是多车协同时的信息共享带宽影响冲突消解的速度。我一般会做一个「场景-变量-约束」的映射表把模糊的描述逼成具体的数学条件场景描述可量化变量约束形式调度中心实时掌握车辆位置通信延迟 t_delayt_delay ≤ 阈值否则路径不可动态调整需求点状态实时上报上报周期 T_report需求更新频率影响重调度触发条件多车信息共享带宽 B、并发连接数 NN × 单连接带宽 ≤ B否则排队网络覆盖盲区覆盖概率 p_cov进入盲区时退化为离线决策这张表的价值在于它逼你回答「5G到底让哪个环节变快了、变准了」。如果映射完发现所有约束都和4G没区别那说明模型没抓住题目的核心。2.2 目标函数怎么设别只盯着总路程基础VRP的目标函数是最小化总行驶距离或总时间。但应急场景下这个目标不够用。题目里「应急」两个字意味着时间窗口和惩罚成本必须进目标函数。我通常设成三层第一层是硬约束——物资必须在时间窗内送达超出直接判不可行。第二层是软约束——超出时间窗但仍在可接受范围内按延迟时长乘以惩罚系数计入成本。第三层才是行驶成本。用数学写出来大概是这样min Z α × Σ(行驶距离) β × Σ(延迟惩罚) γ × Σ(车辆固定成本) s.t. 时间窗约束 载重约束 通信延迟约束 覆盖约束α、β、γ三个权重不是拍脑袋定的。我的经验是先把β设得足够大让算法优先满足时间窗跑通之后再逐步调小观察解的变化。如果β调小后总成本反而下降但延迟上升说明当前路网条件下时间窗和距离存在权衡这个权衡点就是论文里值得讨论的地方。2.3 通信约束怎么嵌进求解过程这是整道题最容易翻车的地方。大部分VRP代码的结构是「初始化种群 → 交叉变异 → 评估适应度 → 选择」通信约束如果只在适应度评估时加一个惩罚项算法很容易在迭代中期卡在不可行解附近。更稳的做法是把通信约束分成两层处理静态约束如覆盖盲区在初始化阶段就排除不可行边动态约束如实时延迟在适应度评估时作为惩罚项。具体来说构建路网邻接矩阵时先把覆盖概率低于阈值的边直接设为无穷大这样算法根本不会选到这些边。实时延迟则用一个简化的排队模型估算import numpy as np def communication_delay(n_vehicles, base_delay0.01, bandwidth100, msg_size1): 简化通信延迟模型 n_vehicles: 当前并发车辆数 base_delay: 基础传播延迟(ms) bandwidth: 总带宽(Mbps) msg_size: 单次消息大小(Mb) # 带宽竞争导致的排队延迟 if n_vehicles * msg_size / bandwidth 1: queue_delay base_delay * (1 / (1 - n_vehicles * msg_size / bandwidth)) else: queue_delay float(inf) # 带宽饱和通信不可用 return queue_delay # 示例5辆车并发时的延迟 for n in range(1, 8): d communication_delay(n) print(f并发车辆数 {n}, 通信延迟 {d:.4f} ms)这段代码的逻辑是当并发车辆数增加时带宽被分摊排队延迟非线性上升。当负载接近带宽上限时延迟趋于无穷代表通信不可用。参数bandwidth和msg_size需要根据题目给的具体数值调整如果没有给就按5G典型值设——带宽100Mbps、单条调度消息1Mb左右。这个模型不追求精确目的是让通信约束在算法里有可感知的梯度而不是一个非0即1的开关。3. 用改进遗传算法跑通最小可行解代码逐段拆3.1 染色体编码与初始化别用自然数编码硬套VRP的编码方式直接决定交叉变异算子好不好写。常见的有自然数编码染色体就是客户访问顺序和分段编码用分隔符切分多车路径。这道题因为有多车和时间窗我建议用分段编码每个基因位代表一个客户编号用0作为车辆分隔符。import random def init_population(n_customers, n_vehicles, pop_size): 初始化种群分段编码0为车辆分隔符 n_customers: 客户数量 n_vehicles: 车辆数量 pop_size: 种群规模 population [] for _ in range(pop_size): customers list(range(1, n_customers 1)) random.shuffle(customers) # 随机插入 n_vehicles-1 个分隔符 for _ in range(n_vehicles - 1): pos random.randint(1, len(customers)) customers.insert(pos, 0) population.append(customers) return population # 示例10个客户3辆车 pop init_population(10, 3, 5) for chrom in pop: print(chrom)这里的关键参数是n_vehicles。如果题目没限制车辆数可以先按客户数除以单车容量估算一个下界再往上浮动20%作为初始值。初始化时随机插入分隔符可能导致某辆车分到0个客户这在后续评估时要处理——要么允许空车增加固定成本要么在解码时跳过。3.2 适应度函数通信惩罚项怎么加才不「淹没」主目标适应度函数是算法的心脏。我见过太多代码把通信惩罚项设得过大导致算法只顾着满足通信约束配送成本爆炸。合理的做法是分层惩罚硬约束违反给大惩罚比如10000软约束违反给中等惩罚比如100通信延迟按实际值线性计入。def fitness(chromosome, distance_matrix, time_windows, comm_delays): 计算适应度 chromosome: 分段编码的染色体 distance_matrix: 距离矩阵 time_windows: 每个客户的时间窗 [(earliest, latest), ...] comm_delays: 每条边的通信延迟 routes decode(chromosome) # 解码成多车路径 total_cost 0 penalty 0 for route in routes: if not route: continue load 0 current_time 0 prev 0 # 从配送中心出发 for customer in route: # 行驶时间 travel_time distance_matrix[prev][customer] / 40.0 # 假设40km/h current_time travel_time # 通信延迟惩罚 comm_penalty comm_delays[prev][customer] * 10 total_cost comm_penalty # 时间窗检查 earliest, latest time_windows[customer - 1] if current_time earliest: current_time earliest # 等待 elif current_time latest: penalty (current_time - latest) * 100 # 软约束惩罚 load 1 # 简化每个客户需求为1 prev customer # 返回配送中心 total_cost distance_matrix[prev][0] return 1.0 / (total_cost penalty 1e-6)参数说明comm_delays矩阵需要提前根据2.3节的延迟模型算好作为静态输入。惩罚系数100和10是经验值前者针对时间窗违反后者针对通信延迟。如果跑出来发现所有解的时间窗违反都很严重说明惩罚系数还不够大或者初始种群质量太差。3.3 交叉与变异顺序交叉配分段编码的适配分段编码不能直接用OX顺序交叉因为分隔符0的位置会乱。我的做法是先把染色体按0切分成多个子路径对每个子路径内部做OX然后再随机重组子路径顺序。def order_crossover(parent1, parent2): 顺序交叉适用于单条路径不含分隔符 size len(parent1) start, end sorted(random.sample(range(size), 2)) child [-1] * size child[start:end] parent1[start:end] fill_pos end % size for gene in parent2: if gene not in child: child[fill_pos] gene fill_pos (fill_pos 1) % size return child def crossover(chrom1, chrom2): 分段交叉按车辆切分后分别交叉 routes1 decode(chrom1) routes2 decode(chrom2) new_routes [] for r1, r2 in zip(routes1, routes2): if len(r1) 1 and len(r2) 1: child_route order_crossover(r1, r2) else: child_route r1 if random.random() 0.5 else r2 new_routes.append(child_route) # 重新编码 result [] for i, route in enumerate(new_routes): result.extend(route) if i len(new_routes) - 1: result.append(0) return result def mutate(chromosome, mutation_rate0.05): 变异随机交换两个客户 chrom chromosome[:] for i in range(len(chrom)): if chrom[i] 0: continue if random.random() mutation_rate: j random.randint(0, len(chrom) - 1) while chrom[j] 0: j random.randint(0, len(chrom) - 1) chrom[i], chrom[j] chrom[j], chrom[i] return chrom交叉和变异之后必须做可行性修复检查每辆车的载重是否超限、客户是否重复或遗漏。修复逻辑我一般写成独立函数在每代进化后统一调用而不是塞在交叉算子里面。3.4 主循环与收敛判断什么时候该停遗传算法的主循环不复杂但停止条件有讲究。固定迭代次数是最简单的但容易浪费算力或过早停止。我通常用「连续N代最优解不变」作为主停止条件同时设一个最大迭代次数兜底。def genetic_algorithm(n_customers, n_vehicles, pop_size100, max_gen500, stall_limit50): population init_population(n_customers, n_vehicles, pop_size) best_fitness 0 stall_count 0 for gen in range(max_gen): # 评估 fitness_values [fitness(ch, dist_mat, tw, comm) for ch in population] current_best max(fitness_values) # 收敛判断 if current_best best_fitness * 1.001: stall_count 1 else: stall_count 0 best_fitness current_best if stall_count stall_limit: print(f第{gen}代收敛最优适应度{best_fitness:.6f}) break # 选择锦标赛 new_pop [] for _ in range(pop_size): i, j random.sample(range(pop_size), 2) winner population[i] if fitness_values[i] fitness_values[j] else population[j] new_pop.append(winner[:]) # 交叉变异 for i in range(0, pop_size - 1, 2): if random.random() 0.8: new_pop[i], new_pop[i1] crossover(new_pop[i], new_pop[i1]) new_pop[i] mutate(new_pop[i]) new_pop[i1] mutate(new_pop[i1]) population new_pop return best_fitness, populationstall_limit设50是一个折中值。设太小容易早停设太大浪费时间。如果问题规模超过50个客户可以适当放宽到80。pop_size一般取客户数的2到4倍100是常用起点。4. 参数调优与结果验证让解从「能跑」到「可信」4.1 三个必调参数种群规模、交叉率、惩罚系数种群规模、交叉率、惩罚系数这三个参数对结果影响最大。我的调参顺序是先固定交叉率0.8和惩罚系数经验值调种群规模种群稳定后再调交叉率最后微调惩罚系数。参数推荐范围影响调整信号种群规模50-200太小早熟太大慢最优解波动大→增大交叉率0.6-0.9太高破坏优良个体收敛慢→适当降低时间窗惩罚50-500太高忽略距离太低不可行不可行解多→增大通信惩罚5-50太高路径绕远通信延迟超标→增大调参时每次只动一个跑5次取平均别一次改三个然后看单次结果——那是玄学不是调参。4.2 用小规模算例验证手算能对上的才敢信代码跑通之后先用3个客户、2辆车的小算例验证。手算最优路径和算法输出对比。如果对不上优先检查距离矩阵和时间窗的索引是否从0开始——这是最常见的翻车点。确认小算例无误后再逐步放大到10客户、20客户观察运行时间和解的质量是否合理。4.3 结果可视化路径图和时间窗甘特图路径图用matplotlib画散点和连线甘特图展示每辆车的到达时间与时间窗的关系。这两张图放进论文里比表格直观得多。画图代码不复杂关键是坐标要对应好别把客户编号和坐标索引搞混。5. 避坑与排查5个让代码翻车的细节现象算法跑完所有客户都没被访问。原因解码函数把分隔符0也当成客户处理了导致路径解析错误。 解决在decode函数里显式跳过0并且检查每条路径是否为空。现象通信延迟惩罚项让总成本变成负数。原因延迟模型在带宽饱和时返回了负值或极大值累加后溢出。 解决在communication_delay函数里加边界判断饱和时返回一个大的正数而不是inf避免数值问题。现象时间窗约束明明满足适应度却很低。原因时间窗索引和客户编号错位比如客户1对应time_windows[1]而不是[0]。 解决统一用「客户编号减一」作为时间窗数组索引并在代码注释里写清楚。现象交叉后出现重复客户或遗漏客户。原因分段交叉时没有做去重和补全。 解决交叉后调用修复函数用集合差集找出遗漏客户随机插入到载重未满的路径中。现象每次运行结果差异很大。原因随机种子没固定或者种群规模太小。 解决调试阶段固定random.seed(42)正式跑结果时至少跑10次取最优和平均。6. 从竞赛题到工程落地一个可复用的验证习惯这道题的价值不止于竞赛。把5G约束换成任何通信条件——比如弱网、间歇连接、边缘计算延迟——模型结构不用大改只需要替换communication_delay函数和对应的约束矩阵。我后来做类似调度问题时养成了一个习惯先把通信条件写成独立的约束模块再把它挂到优化主循环上。这样换场景时只动一个文件不用重写整个算法。验证方法上我推荐用「双跑对比」一组开通信约束一组关通信约束看最优路径差多少。如果差值为零说明通信约束在当前参数下没起作用要么参数设得太松要么模型没接对。这个对比比单纯看收敛曲线有用得多。最后一个具体技巧把每次实验的参数、随机种子、最优适应度写进一个CSV日志。调参时翻日志比翻代码快。我吃过亏——有一次调了二十多组参数最后发现最优解来自第三组但已经记不清第三组的具体配置了只能重跑。从那以后日志成了我的后悔药。希望帮到你。本文还有配套的精品资源点击获取