ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VRPTW改进粒子群算法:融合局部搜索与LNS的MATLAB实现

VRPTW改进粒子群算法:融合局部搜索与LNS的MATLAB实现 做配送路径优化的朋友肯定都有这种体会拿到一个带时间窗的实例改进粒子群算法用上去以后前几十代收敛得挺快可一到后面就卡住不动总距离就差那么一点下不来。时间窗越紧这种情况越明显。我在这类问题上折腾过不少轮最后把局部最优搜索和大规模邻域搜索算法一起嵌入到粒子群框架里才算是把解质量拉到了可接受的范围。这篇内容就围绕这套方案的思路拆解和 MATLAB 代码实现来写适合正在做车辆路径优化、或准备用元启发式算法解决带约束配送问题的同学参考。1. 问题定义VRPTW 为什么和普通路径问题不一样1.1 带时间窗的车辆路径问题到底在优化什么带时间窗的车辆路径问题英文缩写 VRPTW本质上是经典 VRP 的加强版。每条客户需求都有一个最早服务时间和最晚服务时间车辆到达太早可以等到达太晚就属于违反约束必须安排别的路径或别的车辆。我们要做的是在满足每辆车容量限制、总车辆数限制、时间窗限制的前提下找到一组从配送中心出发、服务完所有客户后返回配送中心的路径让总行驶成本尽可能小。写成数学模型大概是这样的假设有 N 个客户编号 1 到 N配送中心编号 0。车辆集合为 K容量为 Q。客户 i 的需求为 q_i服务时间为 s_i时间窗是 [a_i, b_i]。模型决策变量是 x_ijk表示车辆 k 是否从点 i 直接行驶到点 j。目标函数通常取总行驶距离最小也可以加入车辆启用成本。约束至少有这几个每个客户必须被访问且只被访问一次每辆车从配送中心出发完成服务后回到配送中心每条路径上的累计需求不超过车辆容量 Q车辆到达客户 j 的时间不能晚于 b_j如果早于 a_j则允许等待到 a_j 再开始服务。很多人第一次接触这个模型觉得难的不是目标函数而是时间窗约束。因为时间沿路径是累加的前一个客户延误会直接影响后面所有客户。特别在紧凑排程的场景下一个路径段的顺序稍微调整后面一连串客户的到达时间全都变了。所以顺序优化在 VRPTW 里不是一个简单排序问题而是每一步都要做可行的确认。1.2 为什么基础粒子群算法直接用在 VRPTW 上会很难看粒子群算法最初是求解连续优化问题的。粒子位置是一个连续向量速度决定了位置的更新方向和步长。但 VRPTW 的解本质上是离散的路径集合是一组排列组合。直接用经典 PSO 的形式连粒子位置和路径方案的对应关系都定义不出来更谈不上用速度和位置公式做迭代。常规做法是用随机键编码。把每个客户编号对应到粒子向量的一个维度粒子在该维度上的值是一个实数对所有维度的值排序排序结果就得到一个客户访问顺序。比如粒子位置是 [0.3, 0.8, 0.1, 0.5]排序后客户顺序就是 3、1、4、2。拿到顺序以后再按车辆容量和时间窗约束把客户逐步分配到各条路径上。这个编解码机制本身是可行的但它有一个致命弱点粒子在连续空间里的微小变化映射到客户排列上可能是大幅跳变也可能是无效变化。粒子群在迭代后期容易陷入局部最优所有粒子围着一个局部极值打转速度越来越小位置更新变成微小扰动排列顺序几乎不发生变化。我最早用这种基础 PSO 解 50 个客户的算例时结果能跑出来但解质量非常差。对比文献里已知较优解差距经常在 8% 到 15% 左右而且迭代曲线后半段基本是平的。问题不是粒子群本身不行而是它擅长大范围粗搜不擅长局部精修。配送路径优化里很多改进都是小幅调整某条路径的访问顺序比如翻转一段路径、把某个客户挪到另一辆车里这种局部调整恰恰是连续 PSO 编码最不擅长表达的。因此要解好 VRPTW必须给粒子群配上局部搜索和更宏观的扰动机制。2. 算法层拆解改进 PSO、局部最优搜索、大范围搜索如何各司其职2.1 三层结构的整体思路解决上面的问题我的做法是把搜索过程分成三层每一层负责不同粒度的搜索第一层是粒子群负责全局粗搜。粒子群在解空间里保持较强的多样性不断向历史最优和全局最优靠拢把解快速拉到较优区域。第二层是局部最优搜索对当前全局最优解做精细爬山使用 2-opt、Or-opt、2-opt* 这类成熟算子把粒子群找到的好解进一步打磨成局部最优解。第三层是大规模邻域搜索简称 LNS也叫破坏与重建搜索。它每轮移除一定数量的客户再重新插入路径中通过较大的解结构变动来跳出局部最优。这三层不是简单叠加而是按节奏配合。粒子群每迭代若干代就对 gbest 做一次局部最优搜索如果全局最优解连续多代没有更新说明陷入停滞就触发 LNS 对 gbest 或某个优秀粒子做扰动重建。这样做的好处是局部搜索保证了对优质解的深度挖掘LNS 负责在最容易卡死的时候把解结构打散重建粒子群又保证了整个过程的搜索方向不会像纯局部算法那样完全随机。三者的功能有重叠但各自解决的核心问题不同。2.2 局部最优搜索层把好东西做到极致局部搜索层选择算子时我比较过几种常用组合。2-opt 是翻转一条路径里的连续一段对调整路径内部顺序非常有效但要注意翻转后该段拜访顺序完全反过来时间窗可行性需要重新计算。Or-opt 是把路径中的一段连续客户移动到另一个位置适合调整局部序列。2-opt* 则是交换两条路径的尾段它不改变路径内部的客户顺序只是换一个连接点对时间窗的影响相对温和实际效果很好是带时间窗问题的常用算子。在实现时我不会每轮都对所有粒子做局部搜索那样计算量太大。实际操作是每隔固定代数对当前全局最优解执行一轮完整局部搜索每隔更多代再对粒子群中的若干个精英解做一轮。这样做的原因很简单局部搜索的时间复杂度不低特别是 2-opt 需要遍历路径内所有边对对 100 客户规模的算例一轮检查可能涉及数千次候选操作如果每个粒子都做单代计算成本会爆炸。2.3 大规模邻域搜索层破坏、重建再决定要不要接受LNS 的核心是破坏算子和修复算子。每次迭代先执行破坏从当前解中移除 q 个客户然后执行修复把移除的客户重新插入路径中。移除比例一般控制在客户总数的 15% 到 30%。比例太小扰动不足以跳出局部最优比例太大修复时间变长而且解结构被破坏太严重重新修复后质量可能很差。破坏算子我常用两种。一是随机移除实现简单保证随机性二是相关移除把地理位置接近或者时间窗比较接近的客户放在一个移除集合里。相关移除的效果通常更好因为它把本来可能属于同一条路径的客户解开让修复阶段有机会重新规划成更合理的组合。修复阶段用贪婪插入或者 regret-2 插入。贪婪插入特别直观逐个取出被移除的客户尝试插入所有可行位置选择成本增量最小的位置放进去。regret-2 更聪明一些它不只考虑当前最优插入位置还考虑第二优位置优先插入那些如果不现在放之后可能代价大幅上升的客户。修复完成后新解不一定会更优。LNS 的接受准则我通常用模拟退火式接受如果新解更优无条件接受如果新解变差以一定概率接受概率随迭代进行而降低。这个设计很关键它让算法在搜索早期敢于接受较大波动后期逐步收敛到稳定区域。2.4 两层搜索如何写进粒子群主循环我把整个搜索流程组织成一个多层循环。外层是 PSO 迭代内层在特定时机触发局部搜索和 LNS。伪代码逻辑是这样的初始化粒子群计算初始 pbest 和 gbest for iter 1 : maxIter 线性更新惯性权重 w 更新粒子速度和位置 解码粒子位置评估可行性和目标函数 更新 pbest 和 gbest 如果 iter 达到局部搜索周期 对 gbest 执行局部最优搜索 如果 gbest 连续 stallLimit 代没有更新 对 gbest 执行 LNS 重置停滞计数 记录每代最优成本 end这个流程里局部搜索是锦上添花LNS 是穷则思变。前者让好解更好后者让算法有机会跳出死胡同。两者和 PSO 交替配合比我试过的其他混合方式稳定得多。3. 关键 MATLAB 代码与实现细节3.1 粒子位置到配送路径的编解码实现编解码是整个算法里最容易出 bug 的地方。我的做法是随机键编码加顺序分配。粒子位置是一个 1×N 的实数向量对位置值排序后得到客户访问顺序。随后按顺序把客户插入到当前路径中如果插入后违反容量或时间窗就开启一条新路径。这里的关键在于时间窗可行性检查必须被集成到解码过程中。假设当前路径最后访问的客户是 last新的候选客户是 c需要计算从 last 到 c 的行驶时间加上到达 last 的时间和服务时间得到理论到达时间。随后用 max(到达时间, a_c) 得到实际开始服务时间如果开始服务时间大于 b_c则客户 c 不能紧跟 last 放入当前路径。function routes decodeParticle(pos, model) N model.N; dist model.dist; demand model.demand; timeWin model.timeWin; serviceTime model.serviceTime; capacity model.capacity; [~, order] sort(pos); routes {}; curRoute []; curLoad 0; curTime 0; % 从配送中心 0 出发的时刻 currentPoint 0; for i 1:N c order(i); travelTime dist(currentPoint, c); arrival curTime travelTime; start max(arrival, timeWin(c, 1)); if curLoad demand(c) capacity start timeWin(c, 2) curRoute [curRoute, c]; curLoad curLoad demand(c); curTime start serviceTime(c); currentPoint c; else % 关闭当前路径开启新路径 routes{end1} curRoute; curRoute c; curLoad demand(c); arrivalFromDepot dist(0, c); start max(arrivalFromDepot, timeWin(c, 1)); curTime start serviceTime(c); currentPoint c; end end if ~isempty(curRoute) routes{end1} curRoute; end end这个解码器看起来简单但在时间窗很紧的场景下直接把客户按排序结果放入路径可能导致很多客户都开新路径车辆数爆表。我在实际代码里会加一个步骤在放入客户 c 之前不只尝试把 c 放到当前路径末尾而是在当前路径内的所有插入位置之间做一次最小代价插入检查。这样可以显著提高路径的填充率。3.2 粒子群更新主循环代码骨架粒子群的主体代码没有太多玄学重点是边界处理和惯性权重的变化策略。MATLAB 里写起来大概是下面这个样子function [gBest, gBestCost, history] pso_vrptw(model, params) nPop params.nPop; maxIter params.maxIter; wStart params.wStart; wEnd params.wEnd; c1 params.c1; c2 params.c2; N model.N; pPos rand(nPop, N); pVel zeros(nPop, N); pCost inf(nPop, 1); pBest pPos; gBest pPos(1, :); gBestCost inf; for i 1:nPop routes decodeParticle(pPos(i, :), model); cost evaluateRoutes(routes, model); pCost(i) cost; pBest(i, :) pPos(i, :); if cost gBestCost gBestCost cost; gBest pPos(i, :); end end stallCount 0; for iter 1:maxIter w wEnd (wStart - wEnd) * (1 - iter / maxIter); for i 1:nPop pVel(i, :) w * pVel(i, :) ... c1 * rand(1, N) .* (pBest(i, :) - pPos(i, :)) ... c2 * rand(1, N) .* (gBest - pPos(i, :)); pPos(i, :) pPos(i, :) pVel(i, :); % 边界处理将位置限制在合理范围内 pPos(i, :) max(pPos(i, :), 0); pPos(i, :) min(pPos(i, :), 1); routes decodeParticle(pPos(i, :), model); cost evaluateRoutes(routes, model); if cost pCost(i) pCost(i) cost; pBest(i, :) pPos(i, :); end if cost gBestCost gBestCost cost; gBest pPos(i, :); stallCount 0; else stallCount stallCount 1; end end if mod(iter, params.lsFreq) 0 [gBest, gBestCost] localSearchOnSolution(gBest, model); end if stallCount params.stallLimit [gBest, gBestCost] lnsOnSolution(gBest, model); stallCount 0; end history(iter) gBestCost; end end位置上下界我用了 0 和 1因为随机键的位置值本身只在排序中起作用归一化以后不会影响解码结果。粒子速度我会限制在 [-0.5, 0.5] 之间防止粒子位置飞出太远。实践里这个边界限制对收敛速度影响很直观不加的话粒子容易集群性发散。3.3 局部搜索算子的实现先算增量再验时间窗写局部搜索时有个很实用的技巧不要一上来就完整解码整条路径而是先计算候选操作对路径成本的影响再快速判断时间窗是否可行。如果不可行直接跳过。这个技巧能省掉大量无意义的路径重构计算。以 2-opt 为例对一条路径 [0, c_1, ..., c_m, 0]去掉边 (i, i1) 和边 (j, j1)换成 (i, j) 和 (i1, j1)。在距离矩阵已经预计算好的前提下新路径的总距离变化量是delta dist(i, j) dist(i1, j1) - dist(i, i1) - dist(j, j1)如果 delta 不小于 0这个操作对距离没有改善可以直接跳过。如果 delta 小于 0再进一步检查时间窗。由于 2-opt 会反转 i 和 j 之间的客户顺序路径内各节点的到达时间都要重新推算。我会写一个独立函数只对受影响的那一段做前向时间推进而不是重新计算整条路径。function route twoOpt(route, model) n length(route); improved true; while improved improved false; for i 1:n-1 for j i2:n delta model.dist(route(i), route(j)) ... model.dist(route(i1), route(j1)) ... - model.dist(route(i), route(i1)) ... - model.dist(route(j), route(j1)); if delta -1e-9 isFeasibleAfterReverse(route, i1, j, model) route(i1:j) route(j:-1:i1); improved true; end end end end end局部搜索要得到好效果关键是让 2-opt、Or-opt 和 2-opt* 共享同一套时间窗检查函数。时间窗检查函数的逻辑我写成了一个公共模块所有算子在生成候选解后都调用它。这样避免了不同算子在时间窗处理上行为不一致的问题也方便后续调试。3.4 大规模邻域搜索的破坏与修复代码LNS 的破坏阶段相对好写。相关移除的实现是随机选一个客户作为种子然后按距离或时间窗差异从小到大选择其他客户组成移除集合。修复阶段我用贪婪插入。这里给出最简可运行的版本function newSolution lnsDestroyRepair(routes, removedCustomers, model) % 分两步先移除后重新插入 partialRoutes removeCustomersFromRoutes(routes, removedCustomers); newSolution greedyInsertion(partialRoutes, removedCustomers, model); endgreedyInsertion 的实现会对每个待插入客户遍历所有路径的所有插入位置计算成本增量和时间窗可行性选择增量最小的位置插入。每插入一个客户后需要重新计算受影响路径的时间信息。这个过程的复杂度是 O(R × M × P)其中 R 是待插入客户数M 是路径数P 是每条路径的平均位置数。对 100 客户的算例这样一轮修复大概需要几千次候选操作在 MATLAB 里跑一秒钟以内可以完成。regret-2 插入比贪婪插入多了一步对每个待插入客户找出成本增量最小的两个可行位置计算两者的差值。差值越大说明现在不插之后可能付出更大代价优先插入这类客户。这个策略对时间窗紧的情况特别有效因为时间窗紧的客户可供选择的插入位置本来就不多如果不在早期处理后面可能根本没有可行位置。function newSolution regretInsertion(partialRoutes, customerList, model) while ~isempty(customerList) bestCosts inf(length(customerList), 2); for idx 1:length(customerList) c customerList(idx); candidateCosts []; % 遍历所有路径所有位置收集可行的成本增量 for r 1:length(partialRoutes) route partialRoutes{r}; for pos 1:length(route)1 newRoute insertCustomer(route, c, pos); if isFeasible(newRoute, model) costDelta computeDelta(partialRoutes{r}, newRoute, model); candidateCosts(end1) costDelta; %#okAGROW end end end candidateCosts sort(candidateCosts); if length(candidateCosts) 2 bestCosts(idx, 1) candidateCosts(1); bestCosts(idx, 2) candidateCosts(2); elseif length(candidateCosts) 1 bestCosts(idx, 1) candidateCosts(1); bestCosts(idx, 2) candidateCosts(1); end end regret bestCosts(:, 2) - bestCosts(:, 1); [~, bestIdx] max(regret); c customerList(bestIdx); customerList(bestIdx) []; % 将客户 c 放到其最优位置 [partialRoutes, ~] insertCustomerAtBest(partialRoutes, c, model); end newSolution partialRoutes; end实际写代码时我会把 insertCustomerAtBest 这类函数拆得细一些保证每个候选位置都能复用同一个插入子函数。这样主流程看起来更清晰也更方便做单元测试。3.5 算法参数应该如何配置参数配置是这类算法最玄学的部分。我针对 100 客户左右的算例整理了一套比较稳的配置见下表。参数取值说明粒子数 nPop80客户数越多粒子数适当增加迭代次数 maxIter300可以配合停滞条件提前退出惯性权重 w0.9 到 0.4 线性递减前期重探索后期重开发学习因子 c1, c21.5, 1.5均衡个体认知和群体认知局部搜索周期每 5 代一次对 gbest 执行LNS 触发停滞代数15 代gbest 连续 15 代不更新时触发移除比例20%例如 100 客户移除 20 个LNS 接受温度初值当前最优成本的 5%按模拟退火方式衰减局部搜索周期如果太密计算时间会明显增加但解质量提升不多如果太疏gbest 很长时间得不到精修粒子群会在相对差的解上反复震荡。LNS 触发条件我倾向用停滞代数而不是固定周期。因为固定周期在算法前期会打断正常收敛而停滞条件只在算法确实碰到瓶颈时才介入干扰更小。4. 参数配置、测试算例与对比效果4.1 标准测试算例怎么选做 VRPTW 算法对比绕不开 Solomon 标准算例。它把测试数据分成 C 类、R 类和 RC 类三类分别代表聚类分布、随机分布、随机加聚类混合分布。每类又有 100 客户的算例时间窗口的松紧程度也做了区分。C101 这类算例客户在地理上聚集成簇解起来相对容易R101 客户分布比较随机难度更高RC101 是两者混合结构更复杂。我平时做算法验证会在三类里各挑一个代表算例比如 C101、R101 和 RC101。这样既能看算法在聚类结构下的表现也能检验随机分布下的鲁棒性。测试时所有算法用相同的解码函数、相同距离矩阵和相同车辆容量只改变搜索策略这样对比结果才有说服力。4.2 加不加局部搜索和 LNS 的差距有多大不同算法组成的对比趋势很稳定。基础 PSO 解 100 客户的 R101 类算例时总距离偏离已知较优解经常在 5% 到 8% 之间有时车辆数还会多一辆。加入局部最优搜索后解质量提升明显偏离幅度能压缩到 3% 到 5% 左右。再加 LNS 后偏离幅度进一步降到 1% 到 2%。这个趋势在图和表里已经看不出来有几倍差别但放到城市配送的真实场景中总距离每降低 1% 都可能意味着每趟车少跑几公里积累下来很可观。算法组成全局搜索能力局部精修能力跳出局部最优能力典型计算耗时基础 PSO中弱弱低PSO 局部搜索中强较弱中等PSO 局部搜索 LNS中强强中高纯 LNS弱较强强高基础 PSO 的特点是跑得快但容易停在差解上纯 LNS 的特点是能跳出局部最优但搜索方向太随机。两者结合的效果比我单独加大 PSO 迭代次数或单独增加 LNS 迭代次数都要好。4.3 收敛曲线能看出什么我在项目里遇到过一种情况加了 LNS 后迭代曲线反而出现突然上升又下降的毛刺。这不是算法出错而是 LNS 接受较差解导致的。模拟退火机制允许算法在特定温度下接受劣化解所以成本值会短暂上升然后修复插入又把它拉回来。看到曲线有小幅回弹不必紧张只要整体趋势向下就行。反而是那种曲线一直平滑下降、后期完全平坦的版本更可能陷在局部最优里。时间窗越紧收敛后期的平台期越长。因为可行解的邻域结构被压缩能做的局部改进越来越少。这时候 LNS 的移除比例要适当调大比如从 20% 调到 30%。我的经验是时间窗紧的算例LNS 起作用的方式不是让小改动更精细而是把一批客户从原路径中拔出来重新组合形成新的可行结构。5. 实操中的坑及排查方法5.1 解码失败导致车辆数爆炸最常见的 bug 出现在解码阶段。当粒子排序后的客户顺序不佳时按顺序分配客户会不断开新路径到最后一辆车都装不满车辆数远远超过可用车辆数。这个问题在基础 PSO 里很常见原因不是算法流程写错而是解码时只把客户追加到路径末尾从不在已有路径中寻找更优的插入位置。解决办法有两个层面。第一层是解码时使用最小插入策略在把客户加入路径前尝试所有可行插入点选择增量最小的位置。第二层是把车辆数作为目标函数的次要项比如目标函数写成总行驶距离乘以一个大系数再加上车辆数乘以一个惩罚权重。这样算法会主动倾向使用更少的车辆。5.2 时间窗检查的细节错误时间窗检查看着简单实际容易出三个错。第一只用客户的理论到达时间判断是否晚于 b_i却忽略了服务时间和行驶时间的传递关系。第二没有区分到达时间和开始服务时间早到客户有等待时间等待之后的出发时间才是下一段路程的起点。第三2-opt 翻转路径后没有重新计算整段顾客的到达时间导致生成了实际上不可行的解。我建议把所有时间窗检查集中到一个函数里输入是完整路径输出是布尔值。任何算子要修改路径都先调用这个函数。这样排查时不用到处找逻辑分散的时间判断。5.3 LNS 修复阶段性能太慢LNS 每轮修复都要做大量插入尝试如果直接遍历路径并逐个复制数组在 MATLAB 里会特别慢。性能问题通常集中在两个地方一是每次插入都重新计算整条路径的时间序列二是候选位置探索用了不必要的 cell 数组复制。优化手段是先做预计算把每辆车当前路径的到达时间序列存成数组插入客户时只局部更新这个数组候选位置探索则用累加器和索引记录不要频繁扩展数组。还有一个很实在的技巧距离矩阵、时间窗矩阵、服务时间向量都提前做成全局变量或者结构体字段。在粒子群迭代中反复访问这些数据时结构体字段的访问速度比反复从函数参数里解包快不少。5.4 随机性带来的复现问题元启发式算法本身有随机性这没问题。但如果你的代码在两次运行之间结果差异很大就要检查随机种子是否固定了。MATLAB 里用 rng(seed) 可以把随机数发生器重置到固定状态。我在项目开始时固定种子在调试时需要对比不同参数时也固定种子。真正做实验对比时则跑多个随机种子取平均值避免参数调整被随机波动掩盖。另外要注意MATLAB 并行工具箱里的 parfor 循环如果配合随机函数使用每个工作进程的随机流需要单独设置。否则并行版本和串行版本的结果可能对不上。5.5 惩罚系数的平衡问题很多人在目标函数里加入时间窗违反惩罚项。惩罚系数太小算法会大量生成超时解因为超时付出的代价低于路径缩短带来的收益惩罚系数太大算法又不敢尝试任何轻微违反约束的解搜索空间被严重限制。我的做法是让惩罚系数和总距离处于同一数量级然后用一个加权因子慢慢递增。这种方式在实际使用中比固定惩罚系数更稳既能前期探索较广区域又能在后期收紧约束。6. 后续扩展与个人心得6.1 这套框架怎么扩展到更复杂的场景这套PSO 局部搜索 LNS的框架扩展性比我预想的好。加一个硬时间窗改成软时间窗的功能只需要在时间窗检查函数里把不可行判断改为可接受但需要惩罚加入多车型只需要在解码时给每辆车增加车型属性加入客户偏好只需调整插入位置选择逻辑。核心的粒子群迭代结构、局部搜索算子和 LNS 破坏修复机制都不用大改。我还在一个带时间窗和多配送中心的项目里用过类似结构改动主要集中在解码函数和局部搜索算子的适用范围。整体搜索框架几乎原样搬了过去效果也稳定。6.2 我在实际项目中坚持的几个原则第一先跑通基础 PSO再加上局部搜索最后加 LNS。别开始就堆所有模块出了问题根本不知道是谁引起的。第二参数调优时一次只改一个参数先用默认参数跑一遍再逐个调。第三每次改完代码都要用一个小算例快速验证可行性再用大算例跑完整实验。第四时间窗问题的局部搜索和 LNS 都必须建立在一套统一的时间窗检查函数上各模块自己写检查逻辑后期会苦不堪言。关于 LNS 的使用我还有一个小技巧不要每次触发 LNS 都对 gbest 做完整重建可以把 LNS 用在几个不同粒子位置抽出的精英解上增加解结构的多样性。这在实际运行中让最终解质量比单纯作用在 gbest 上更好一点而且多消耗的计算时间并不多。6.3 最后再分享一个调试经验如果你发现改进粒子群算法的结果反而不如基础 PSO不要先怀疑局部搜索或者 LNS 写错了。先看解码器对同样粒子位置是不是每次都给出稳定解再看 gbest 在局部搜索后是否有同步更新。我踩过一次很深的坑局部搜索改善了 gbest但粒子群主循环里的 gbestCost 没有同步更新结果粒子群还在向一个落后的 gbest 位置靠拢搜索方向全是错的。这类状态同步问题在多层结构里特别容易出现建议把 gbest、gbestCost、gbestRoutes 放在同一个结构体里管理避免多个变量不同步。
返回列表