1. 项目概述:遗传算法在带约束车辆路径规划中的应用
带容量和体积约束的车辆路径规划问题(CVRP)是物流优化领域的经典难题。作为一名长期从事物流系统优化的工程师,我经常需要处理这类NP难问题。在实际项目中,我们往往需要在有限时间内找到近似最优解,而遗传算法(GA)因其强大的全局搜索能力成为我的首选工具。
CVRP问题的核心挑战在于:如何在满足每辆车载重和容积限制的前提下,规划出总行驶距离最短的配送路线。这看似简单的要求,在实际操作中却异常复杂——当客户点超过50个时,可能的路径组合数量就会超过宇宙中原子的总数!传统精确算法对此束手无策,而遗传算法通过模拟自然进化过程,能在合理时间内给出优质解。
本文将从工程实践角度,详细解析如何用MATLAB实现遗传算法求解CVRP问题。不同于教科书式的理论介绍,我会重点分享在实际项目中积累的调参技巧和性能优化经验,这些都是在文献中难以找到的实战干货。
2. 问题建模与算法设计
2.1 CVRP问题的数学表述
要使算法有效,首先需要建立精确的数学模型。CVRP可以形式化为:
- 设图G=(V,E),V={0,1,...,n},其中0代表仓库,1~n代表客户点
- 每条边(i,j)∈E有距离dᵢⱼ
- 每辆车容量为Q,容积为V
- 每个客户i有需求qᵢ(重量)和vᵢ(体积)
- 目标是最小化总行驶距离,同时满足:
- 每辆车负载≤Q
- 每辆车体积≤V
- 每个客户被访问一次
- 所有路径从仓库出发并返回仓库
在MATLAB中,我们这样定义问题参数:
matlab复制% 基础参数设置
numCustomers = 50; % 客户数量
vehicleCapacity = 100; % 车辆载重上限
vehicleVolume = 80; % 车辆容积上限
maxVehicles = 10; % 最大可用车辆数
% 生成随机客户需求(实际项目中替换为真实数据)
customerDemands = randi([1,10],numCustomers,2); % 第一列重量,第二列体积
2.2 遗传算法的特殊改造
标准遗传算法需要针对CVRP进行以下关键改造:
-
染色体编码:采用基于路径的编码方式,用0作为分隔符。例如:
[0,3,7,12,0,5,9,0,...]表示第一辆车路线0→3→7→12→0,第二辆车0→5→9→0... -
适应度函数:需要同时考虑距离和约束违反情况:
matlab复制function fitness = calculateFitness(routes, distMatrix, demands, capacity, volume) totalDistance = 0; penalty = 0; for r = 1:length(routes) route = routes{r}; currentLoad = 0; currentVol = 0; % 计算路径距离 for i = 1:length(route)-1 totalDistance = totalDistance + distMatrix(route(i),route(i+1)); end % 检查约束 for node = route(2:end-1) currentLoad = currentLoad + demands(node,1); currentVol = currentVol + demands(node,2); end if currentLoad > capacity penalty = penalty + 1000*(currentLoad - capacity); end if currentVol > volume penalty = penalty + 1000*(currentVol - volume); end end fitness = 1/(totalDistance + penalty + 0.1); % 避免除零 end -
遗传操作定制:
- 选择:采用锦标赛选择,保持种群多样性
- 交叉:使用顺序交叉(OX),保留路径片段
- 变异:采用交换变异,但需保证不违反约束
3. MATLAB实现细节
3.1 算法主框架
以下是遗传算法的主循环结构:
matlab复制function [bestSolution, bestFitness] = gaForCVRP(params, distMatrix, demands)
% 初始化种群
population = initializePopulation(params);
for gen = 1:params.maxGenerations
% 评估适应度
fitness = evaluatePopulation(population, distMatrix, demands, ...
params.vehicleCapacity, params.vehicleVolume);
% 选择
parents = tournamentSelection(population, fitness, params.tournamentSize);
% 交叉
offspring = crossover(parents, params.crossoverProb);
% 变异
offspring = mutate(offspring, params.mutationProb);
% 新一代种群
population = [parents; offspring];
% 精英保留
population = elitism(population, fitness, params.eliteCount);
% 记录最佳解
[bestFitness, idx] = max(fitness);
bestSolution = population{idx};
% 显示进度
if mod(gen,10)==0
fprintf('Generation %d: Best Fitness = %.4f\n', gen, bestFitness);
end
end
end
3.2 关键组件实现
3.2.1 种群初始化
有效的初始化能加速收敛。我们采用以下策略:
- 最近邻法生成部分优质个体
- 随机生成保持多样性
- 确保所有个体满足车辆数量约束
matlab复制function population = initializePopulation(params)
population = cell(params.popSize,1);
for i = 1:params.popSize
if i <= params.popSize/5 % 20%个体使用最近邻法
population{i} = nearestNeighborInit(params);
else % 其余随机生成
population{i} = randomInit(params);
end
end
end
3.2.2 顺序交叉(OX)实现
OX交叉能有效保留路径片段:
matlab复制function offspring = oxCrossover(parent1, parent2)
% 将路径转换为客户序列(去掉仓库)
seq1 = parent1(parent1~=0);
seq2 = parent2(parent2~=0);
% 选择交叉片段
n = length(seq1);
points = sort(randperm(n,2));
% 创建子代
child1 = zeros(1,n);
child2 = zeros(1,n);
% 复制片段
child1(points(1):points(2)) = seq1(points(1):points(2));
child2(points(1):points(2)) = seq2(points(1):points(2));
% 填充剩余位置
fillChild(child1, seq2, points);
fillChild(child2, seq1, points);
% 转换回带仓库的路径格式
offspring = {addDepots(child1), addDepots(child2)};
end
3.2.3 约束处理技巧
在评估和修复解时,采用分级惩罚策略:
- 轻微超载:线性惩罚
- 严重超载:指数惩罚
- 车辆超限:直接淘汰
matlab复制function isValid = checkConstraints(route, demands, capacity, volume)
currentLoad = 0;
currentVol = 0;
for node = route(2:end-1)
currentLoad = currentLoad + demands(node,1);
currentVol = currentVol + demands(node,2);
if currentLoad > capacity || currentVol > volume
isValid = false;
return;
end
end
isValid = true;
end
4. 参数调优与性能提升
4.1 关键参数经验值
通过数百次实验,总结出以下参数范围:
| 参数 | 推荐值 | 影响分析 |
|---|---|---|
| 种群大小 | 50-200 | 过小易早熟,过大会增加计算时间 |
| 交叉概率 | 0.7-0.9 | 过高会破坏优良模式,过低收敛慢 |
| 变异概率 | 0.01-0.1 | 保持种群多样性的关键 |
| 锦标赛规模 | 3-5 | 平衡选择压力与多样性 |
| 精英保留数 | 2-5 | 防止优秀个体丢失 |
实际项目中建议采用参数自适应策略:
matlab复制function params = adaptiveParameters(params, gen, maxGen)
% 动态调整变异概率
params.mutationProb = 0.1*(1 - gen/maxGen);
% 后期增加选择压力
if gen > maxGen*0.7
params.tournamentSize = min(7, params.tournamentSize+1);
end
end
4.2 加速技巧
- 距离矩阵预处理:
matlab复制% 预先计算所有点间距离
function distMatrix = precomputeDistances(locations)
n = size(locations,1);
distMatrix = zeros(n);
for i = 1:n
for j = 1:n
distMatrix(i,j) = norm(locations(i,:)-locations(j,:));
end
end
end
- 并行评估:
matlab复制% 使用parfor并行计算适应度
fitness = zeros(popSize,1);
parfor i = 1:popSize
fitness(i) = calculateFitness(population{i}, distMatrix, demands, capacity, volume);
end
- 记忆化技术:缓存已评估个体的适应度值
5. 结果分析与可视化
5.1 典型输出示例
运行算法后,我们得到如下优化结果:
code复制最优路径:
车辆1: 0->15->32->8->24->0 (载重98/100, 体积75/80)
车辆2: 0->5->19->42->0 (载重95/100, 体积72/80)
车辆3: 0->7->29->11->0 (载重99/100, 体积78/80)
总距离: 458.7km
5.2 可视化实现
使用MATLAB绘图展示优化过程:
matlab复制function plotResults(bestSolution, locations)
figure;
colors = lines(length(bestSolution));
% 绘制客户点
scatter(locations(2:end,1), locations(2:end,2), 'filled');
hold on;
% 绘制仓库
scatter(locations(1,1), locations(1,2), 100, 'rp', 'filled');
% 绘制路径
for i = 1:length(bestSolution)
route = bestSolution{i};
plot(locations(route,1), locations(route,2), 'Color', colors(i,:), 'LineWidth', 2);
end
title('优化后的车辆路径');
xlabel('X坐标'); ylabel('Y坐标');
grid on;
end
5.3 性能评估指标
建议跟踪以下指标评估算法表现:
- 收敛曲线:观察适应度随迭代的变化
- 计算时间:单次迭代平均耗时
- 约束满足率:满足所有约束的解占比
- 资源利用率:车辆载重和容积的平均使用率
6. 工程实践中的挑战与解决方案
6.1 大规模问题处理
当客户点超过500时,需要采用以下策略:
- 分治策略:先聚类再分区优化
- 精英移民:多个子种群并行进化
- 启发式初始化:使用节约算法生成初始解
6.2 动态环境适应
实际配送中常有实时变化,解决方案:
matlab复制function adaptToChanges(bestSolution, newOrders)
% 1. 固定已执行路径
% 2. 将新订单插入现有路径(检查约束)
% 3. 无法插入则启动局部重优化
for order = newOrders
inserted = false;
for i = 1:length(bestSolution)
route = bestSolution{i};
% 尝试插入最佳位置
[newRoute, success] = tryInsert(route, order);
if success
bestSolution{i} = newRoute;
inserted = true;
break;
end
end
if ~inserted
% 启动局部重优化
bestSolution = localReoptimize(bestSolution, order);
end
end
end
6.3 多目标优化
除距离外,还需考虑:
- 车辆使用成本
- 时间窗约束
- 司机工作时间
可采用NSGA-II等多目标遗传算法框架。
7. 完整代码结构
项目建议的文件结构:
code复制/CVRP_GA
├── main.m % 主脚本
├── initializePopulation.m % 种群初始化
├── calculateFitness.m % 适应度计算
├── crossover.m % 交叉操作
├── mutate.m % 变异操作
├── selection.m % 选择操作
├── utils/ % 工具函数
│ ├── plotResults.m
│ ├── precomputeDistances.m
│ └── checkConstraints.m
└── data/ % 测试数据
├── customerLocations.csv
└── customerDemands.csv
在实现过程中,我特别建议将算法核心与问题定义分离,这样同一套遗传算法框架可以方便地应用于其他变种VRP问题。