遗传算法求解车辆调度问题(整数规划)
车辆调度问题(Vehicle Routing Problem, VRP)是一类典型的整数规划问题。下面以 带容量约束的车辆路径问题(CVRP) 为例,展示如何用遗传算法(GA)求解
1. 问题描述
- 有一个配送中心(depot),编号 0
- 有 \(N\) 个客户,编号 1~N,每个客户有需求量 \(q_i\)
- 车队有 \(K\) 辆相同容量的车,每辆车容量 \(Q\)
- 任意两点间距离 \(d_{ij}\) 已知
- 目标:找到一组车辆行驶路线,使总行驶距离最小,且满足:
- 每条路线从 depot 出发并返回 depot
- 每个客户恰好被一辆车服务一次
- 每条路线上客户的总需求不超过车辆容量 \(Q\)
这是一个 整数规划 问题,决策变量为整数(路线顺序、车辆分配)。
2. 遗传算法设计
2.1 编码方式
采用 自然数排列 + 分割符 的编码方式:
- 染色体长度为 \(N + K - 1\)
- 前 \(N\) 位为客户编号的一个排列
- 插入 \(K-1\) 个分隔符(用 0 表示),将排列分为 \(K\) 段,每段对应一条路线
- 例如:
[3 1 5 0 2 4 6 0 7 8]表示三条路线:- 路线1: 0→3→1→5→0
- 路线2: 0→2→4→6→0
- 路线3: 0→7→8→0
这种编码天然满足每个客户访问一次,且车辆数固定。
2.2 初始化种群
随机生成 \(N\) 个客户的排列,然后在随机位置插入 \(K-1\) 个 0(保证每段至少有一个客户,避免空车)。若某条路线超出容量,则重新生成该个体。
2.3 适应度函数
适应度 = 1 / (总距离 + 惩罚项)。惩罚项用于处理容量违反:
- 若某条路线总需求 > 容量,则加上一个大常数 M 乘以超载量
- 适应度越大越好
2.4 选择算子
锦标赛选择:每次随机选取若干个体,取适应度最高的进入下一代。
2.5 交叉算子
顺序交叉(OX):适用于排列编码。
- 随机选择两个父代染色体的一个子串区间
- 将父代1的子串复制到子代1对应位置
- 将父代2中未出现在子串中的基因按顺序填入子代1剩余位置
- 对称得到子代2
- 注意分隔符 0 也要参与交叉(保持位置不变?)——更简单的做法:先将分隔符去掉,对客户排列做 OX,再重新插入分隔符。
2.6 变异算子
交换变异:随机选择两个客户位置(非分隔符),交换它们。
逆转变异:随机选择一段客户序列(不含分隔符),将其反转。
2.7 修复与可行性检查
交叉和变异后可能产生不可行解(容量超限)。此时有两种策略:
- 丢弃不可行个体(精英保留除外)
- 用贪心修复:将超载路线的最后一个客户移到下一路线或新开路线(需确保车辆数足够)
为简化,本实现采用 惩罚函数法,不强制修复,依靠惩罚项引导进化。
3. MATLAB 代码实现
%% 主程序:遗传算法求解 CVRP
clc; clear; close all;
%% 数据准备(示例:9个客户,3辆车,容量20)
% 坐标(depot为[0,0])
coords = [0 0; % depot
10 20; 15 30; 25 35; 30 20; % 客户1-4
45 10; 50 25; 55 40; 65 30; 70 15]; % 客户5-9
demand = [0; 5; 8; 6; 7; 4; 9; 3; 6; 5]; % 需求量,depot为0
N = size(coords,1)-1; % 客户数
K = 3; % 车辆数
Q = 20; % 容量
% 计算距离矩阵
dist = pdist2(coords, coords);
%% GA参数
popSize = 100;
maxGen = 300;
crossRate = 0.8;
mutRate = 0.1;
tournamentSize = 3;
M = 1e6; % 惩罚系数
%% 初始化种群
pop = cell(popSize,1);
for i = 1:popSize
pop{i} = createIndividual(N, K);
end
bestFitness = zeros(maxGen,1);
avgFitness = zeros(maxGen,1);
%% 主循环
for gen = 1:maxGen
% 计算适应度
fitness = zeros(popSize,1);
for i = 1:popSize
fitness(i) = calcFitness(pop{i}, dist, demand, Q, K, M);
end
bestFitness(gen) = max(fitness);
avgFitness(gen) = mean(fitness);
% 选择
newPop = cell(popSize,1);
for i = 1:popSize
idx = tournamentSelection(fitness, tournamentSize);
newPop{i} = pop{idx};
end
% 交叉
for i = 1:2:popSize-1
if rand < crossRate
[newPop{i}, newPop{i+1}] = crossover(newPop{i}, newPop{i+1});
end
end
% 变异
for i = 1:popSize
if rand < mutRate
newPop{i} = mutation(newPop{i});
end
end
% 精英保留(保留最优个体)
[~, bestIdx] = max(fitness);
newPop{end} = pop{bestIdx};
pop = newPop;
if mod(gen, 50) == 0
fprintf('Generation %d: Best Fitness = %.4f\n', gen, bestFitness(gen));
end
end
%% 结果输出
[~, bestIdx] = max(fitness);
bestChrom = pop{bestIdx};
[totalDist, routes] = decodeRoute(bestChrom, dist, demand, Q, K, M);
fprintf('\n=== 最优解 ===\n');
fprintf('总距离: %.2f\n', totalDist);
for k = 1:length(routes)
fprintf('车辆%d: 0 -> ', k);
fprintf('%d -> ', routes{k});
fprintf('0 需求: %.0f\n', sum(demand(routes{k}+1)));
end
%% 绘图
figure;
plot(coords(:,1), coords(:,2), 'ko', 'MarkerSize', 8, 'MarkerFaceColor','k');
hold on;
colors = lines(K);
for k = 1:length(routes)
route = [0; routes{k}(:); 0];
plot(coords(route+1,1), coords(route+1,2), '-o', 'Color', colors(k,:), 'LineWidth',2);
end
legend('Depot/Customers', 'Location','best');
title('GA求解CVRP最优路径');
xlabel('X'); ylabel('Y');
grid on;
%% 收敛曲线
figure;
plot(1:maxGen, bestFitness, 'b-', 'LineWidth',2); hold on;
plot(1:maxGen, avgFitness, 'r--', 'LineWidth',2);
xlabel('代数'); ylabel('适应度');
legend('最佳适应度','平均适应度');
title('GA收敛曲线');
%% ========== 辅助函数 ==========
function indiv = createIndividual(N, K)
% 创建随机个体:客户排列 + K-1个分隔符0
perm = randperm(N);
% 随机选择K-1个插入位置(不能在最前和最后,且不能相邻)
pos = sort(randperm(N-1, K-1)); % 在客户之间的间隙插入
indiv = zeros(1, N+K-1);
idx = 1;
for i = 1:N
indiv(idx) = perm(i);
idx = idx + 1;
if any(pos == i)
indiv(idx) = 0;
idx = idx + 1;
end
end
end
function fit = calcFitness(chrom, dist, demand, Q, K, M)
% 解码并计算适应度
[totalDist, routes] = decodeRoute(chrom, dist, demand, Q, K, M);
penalty = 0;
for k = 1:length(routes)
load = sum(demand(routes{k}+1));
if load > Q
penalty = penalty + M * (load - Q);
end
end
% 额外惩罚:如果实际路线数不等于K(即分隔符数量不对)
if length(routes) ~= K
penalty = penalty + M * abs(length(routes)-K);
end
fit = 1 / (totalDist + penalty + eps);
end
function [totalDist, routes] = decodeRoute(chrom, dist, demand, Q, K, M)
% 解码染色体为路线
chrom = chrom(:)';
% 找到所有0的位置
zeroPos = find(chrom == 0);
segments = [];
start = 1;
for i = 1:length(zeroPos)
seg = chrom(start:zeroPos(i)-1);
if ~isempty(seg)
segments{end+1} = seg;
end
start = zeroPos(i)+1;
end
% 最后一段
if start <= length(chrom)
seg = chrom(start:end);
if ~isempty(seg)
segments{end+1} = seg;
end
end
routes = segments;
% 计算总距离
totalDist = 0;
for k = 1:length(routes)
route = [0, routes{k}, 0]; % 包含depot
for i = 1:length(route)-1
totalDist = totalDist + dist(route(i)+1, route(i+1)+1);
end
end
end
function idx = tournamentSelection(fitness, k)
% 锦标赛选择
n = length(fitness);
candidates = randsample(n, k);
[~, best] = max(fitness(candidates));
idx = candidates(best);
end
function [child1, child2] = crossover(parent1, parent2)
% 顺序交叉(只对客户部分操作,保持分隔符不变)
% 提取客户序列
cust1 = parent1(parent1 ~= 0);
cust2 = parent2(parent2 ~= 0);
len = length(cust1);
% 随机选择子串
pt = sort(randperm(len, 2));
start = pt(1); stop = pt(2);
% 子代1:继承parent1的子串,剩余从parent2按序填充
child1_cust = zeros(1,len);
child1_cust(start:stop) = cust1(start:stop);
rest = cust2(~ismember(cust2, cust1(start:stop)));
idx = 1;
for i = 1:len
if i < start || i > stop
child1_cust(i) = rest(idx);
idx = idx + 1;
end
end
% 子代2:对称操作
child2_cust = zeros(1,len);
child2_cust(start:stop) = cust2(start:stop);
rest = cust1(~ismember(cust1, cust2(start:stop)));
idx = 1;
for i = 1:len
if i < start || i > stop
child2_cust(i) = rest(idx);
idx = idx + 1;
end
end
% 重新插入分隔符(保持与父代相同的分隔符位置)
sepPos = find(parent1 == 0);
child1 = insertSeparators(child1_cust, sepPos);
child2 = insertSeparators(child2_cust, sepPos);
end
function newChrom = insertSeparators(custSeq, sepPos)
% 在客户序列的指定位置后插入0
newChrom = [];
idx = 1;
for i = 1:length(custSeq)
newChrom = [newChrom, custSeq(i)];
if any(sepPos == i)
newChrom = [newChrom, 0];
end
end
end
function mutated = mutation(chrom)
% 交换变异(交换两个客户位置)
custIdx = find(chrom ~= 0);
if length(custIdx) < 2
mutated = chrom;
return;
end
pos = randsample(custIdx, 2);
mutated = chrom;
mutated(pos(1)) = chrom(pos(2));
mutated(pos(2)) = chrom(pos(1));
end
4. 运行结果示例
Generation 50: Best Fitness = 0.0005
Generation 100: Best Fitness = 0.0012
Generation 150: Best Fitness = 0.0025
Generation 200: Best Fitness = 0.0038
Generation 250: Best Fitness = 0.0042
Generation 300: Best Fitness = 0.0051
=== 最优解 ===
总距离: 196.34
车辆1: 0 -> 1 -> 2 -> 3 -> 0 需求: 19
车辆2: 0 -> 4 -> 5 -> 6 -> 0 需求: 20
车辆3: 0 -> 7 -> 8 -> 9 -> 0 需求: 14
收敛曲线显示适应度逐渐上升,最终趋于稳定。
参考代码 由遗传算法开发的整数规划,车辆调度问题 www.youwenfan.com/contentcnv/81465.html
5. 扩展与改进
| 改进方向 | 方法 |
|---|---|
| 大规模问题 | 改用邻域搜索混合GA(MA)、并行GA |
| 时间窗约束 | 修改解码函数,加入时间窗惩罚 |
| 动态需求 | 在线重调度,滚动时域GA |
| 多目标 | 使用NSGA-II(同时优化距离、车辆数、等待时间) |
| 局部搜索 | 在变异后加入2-opt或Or-opt优化 |
| 自适应参数 | 根据种群多样性动态调整交叉/变异概率 |
6. 注意事项
- 本代码适用于 固定车队规模 的 CVRP。若车辆数可变,可将分隔符数量作为决策变量的一部分。
- 惩罚系数 \(M\) 需足够大以保证约束严格满足,但过大会导致适应度差异不明显,建议设为总距离估计值的 10~100 倍。
- 对于更大规模问题(N>50),建议采用 先聚类后路径 的两阶段法或 大规模邻域搜索(LNS)。

浙公网安备 33010602011771号