使用MATLAB实现遗传算法解决带时间窗的车辆路径问题(GA-VRPTW)
% GA for Vehicle Routing Problem with Time Windows (VRPTW)
% 遗传算法解决带时间窗的车辆路径问题
clear;
close all;
clc;
%% 问题参数设置
rng(42); % 设置随机数种子以确保结果可重现
% 顾客数量(不包括仓库)
nCustomers = 20;
% 仓库坐标(起点和终点)
depot = [50, 50];
% 随机生成顾客坐标、需求和服务时间
customers = zeros(nCustomers, 6); % [x, y, demand, ready_time, due_time, service_time]
for i = 1:nCustomers
customers(i, 1:2) = rand(1, 2) * 100; % x,y坐标在0-100范围内
customers(i, 3) = randi([1, 5]); % 需求1-5
customers(i, 4) = randi([0, 50]); % 最早服务时间
customers(i, 5) = customers(i, 4) + randi([50, 100]); % 最晚服务时间
customers(i, 6) = randi([5, 15]); % 服务时间5-15
end
% 车辆参数
vehicleCapacity = 20; % 车辆容量
vehicleSpeed = 1; % 车辆速度(单位距离/单位时间)
nVehicles = 5; % 车辆数量
% 计算距离矩阵
nNodes = size(customers, 1) + 1; % 包括仓库
distMatrix = zeros(nNodes, nNodes);
coordinates = [depot; customers(:, 1:2)]; % 所有节点坐标
for i = 1:nNodes
for j = 1:nNodes
distMatrix(i, j) = sqrt((coordinates(i, 1) - coordinates(j, 1))^2 + ...
(coordinates(i, 2) - coordinates(j, 2))^2);
end
end
%% 遗传算法参数
popSize = 100; % 种群大小
maxGen = 200; % 最大迭代次数
crossoverProb = 0.8; % 交叉概率
mutationProb = 0.1; % 变异概率
eliteRatio = 0.1; % 精英比例
%% 初始化种群
population = initPopulation(popSize, nCustomers);
%% 遗传算法主循环
bestFitnessHistory = zeros(maxGen, 1); % 记录每代最佳适应度
avgFitnessHistory = zeros(maxGen, 1); % 记录每代平均适应度
for gen = 1:maxGen
% 评估种群
[fitness, totalDistance, nViolations] = evaluatePopulation(population, customers, depot, ...
distMatrix, vehicleCapacity, vehicleSpeed);
% 记录统计信息
bestFitnessHistory(gen) = min(fitness);
avgFitnessHistory(gen) = mean(fitness);
% 选择精英
nElite = round(eliteRatio * popSize);
[~, eliteIdx] = sort(fitness);
elite = population(eliteIdx(1:nElite), :);
% 选择父代(锦标赛选择)
parentIds = tournamentSelection(fitness, popSize - nElite, 3);
parents = population(parentIds, :);
% 交叉
offspring = crossover(parents, crossoverProb);
% 变异
offspring = mutate(offspring, mutationProb);
% 创建新一代种群
population = [elite; offspring];
% 显示进度
if mod(gen, 10) == 0
fprintf('Generation %d: Best Fitness = %.2f, Avg Fitness = %.2f\n', ...
gen, bestFitnessHistory(gen), avgFitnessHistory(gen));
end
end
%% 提取最佳解
[fitness, totalDistance, nViolations, routes, arrivalTimes] = evaluatePopulation(population, customers, depot, ...
distMatrix, vehicleCapacity, vehicleSpeed);
[bestFitness, bestIdx] = min(fitness);
bestSolution = population(bestIdx, :);
bestRoutes = routes{bestIdx};
bestArrivalTimes = arrivalTimes{bestIdx};
%% 可视化结果
% 绘制收敛曲线
figure;
plot(1:maxGen, bestFitnessHistory, 'b-', 'LineWidth', 2);
hold on;
plot(1:maxGen, avgFitnessHistory, 'r--', 'LineWidth', 2);
xlabel('Generation');
ylabel('Fitness');
legend('Best Fitness', 'Average Fitness');
title('Convergence Curve');
grid on;
% 绘制最佳路径
figure;
hold on;
% 绘制仓库
plot(depot(1), depot(2), 'ks', 'MarkerSize', 10, 'MarkerFaceColor', 'y');
text(depot(1)+2, depot(2)+2, 'Depot', 'FontWeight', 'bold');
% 绘制顾客点
for i = 1:nCustomers
plot(customers(i, 1), customers(i, 2), 'bo', 'MarkerSize', 8, 'MarkerFaceColor', 'c');
text(customers(i, 1)+2, customers(i, 2)+2, sprintf('%d', i), 'FontSize', 8);
% 绘制时间窗
rectangle('Position', [customers(i,1)-3, customers(i,2)-3, 6, 6], ...
'Curvature', [1, 1], 'EdgeColor', 'r', 'LineWidth', 1.5);
end
% 绘制路径
colors = lines(length(bestRoutes));
for v = 1:length(bestRoutes)
route = bestRoutes{v};
if isempty(route)
continue;
end
% 绘制从仓库到第一个顾客
plot([depot(1), customers(route(1), 1)], [depot(2), customers(route(1), 2)], ...
'Color', colors(v, :), 'LineWidth', 2);
% 绘制顾客之间的路径
for i = 1:length(route)-1
plot([customers(route(i), 1), customers(route(i+1), 1)], ...
[customers(route(i), 2), customers(route(i+1), 2)], ...
'Color', colors(v, :), 'LineWidth', 2);
end
% 绘制从最后一个顾客返回仓库
plot([customers(route(end), 1), depot(1)], [customers(route(end), 2), depot(2)], ...
'Color', colors(v, :), 'LineWidth', 2);
% 标记车辆路径
text(mean([depot(1); customers(route, 1)]), ...
mean([depot(2); customers(route, 2)]), ...
sprintf('V%d', v), 'Color', colors(v, :), 'FontWeight', 'bold');
end
title(sprintf('Best Solution: Distance=%.2f, Violations=%d', totalDistance(bestIdx), nViolations(bestIdx)));
axis equal;
grid on;
hold off;
% 显示路径详情
fprintf('\n=== Best Solution Details ===\n');
fprintf('Total Distance: %.2f\n', totalDistance(bestIdx));
fprintf('Number of Constraint Violations: %d\n', nViolations(bestIdx));
fprintf('Number of Vehicles Used: %d\n', length(bestRoutes));
for v = 1:length(bestRoutes)
if ~isempty(bestRoutes{v})
fprintf('Vehicle %d: Depot -> ', v);
for i = 1:length(bestRoutes{v})
customer = bestRoutes{v}(i);
fprintf('%d (Arrival: %.2f) -> ', customer, bestArrivalTimes{v}(i));
end
fprintf('Depot\n');
end
end
%% 初始化种群函数
function population = initPopulation(popSize, nCustomers)
population = zeros(popSize, nCustomers);
for i = 1:popSize
population(i, :) = randperm(nCustomers);
end
end
%% 评估种群函数
function [fitness, totalDistance, nViolations, allRoutes, allArrivalTimes] = ...
evaluatePopulation(population, customers, depot, distMatrix, vehicleCapacity, vehicleSpeed)
popSize = size(population, 1);
nCustomers = size(customers, 1);
fitness = zeros(popSize, 1);
totalDistance = zeros(popSize, 1);
nViolations = zeros(popSize, 1);
allRoutes = cell(popSize, 1);
allArrivalTimes = cell(popSize, 1);
for i = 1:popSize
chromosome = population(i, :);
[routes, arrivalTimes] = decodeChromosome(chromosome, customers, depot, ...
distMatrix, vehicleCapacity, vehicleSpeed);
% 计算总距离和违反约束数量
[dist, violations] = calculateFitness(routes, arrivalTimes, customers, depot, distMatrix);
totalDistance(i) = dist;
nViolations(i) = violations;
fitness(i) = dist + 1000 * violations; % 惩罚项系数设为1000
allRoutes{i} = routes;
allArrivalTimes{i} = arrivalTimes;
end
end
%% 染色体解码函数
function [routes, arrivalTimes] = decodeChromosome(chromosome, customers, depot, ...
distMatrix, vehicleCapacity, vehicleSpeed)
nCustomers = size(customers, 1);
routes = {};
arrivalTimes = {};
% 当前车辆路径
currentRoute = [];
currentLoad = 0;
currentTime = 0;
currentNode = 1; % 起始节点是仓库(节点1)
customerArrivalTimes = [];
for i = 1:nCustomers
customerId = chromosome(i);
customerNode = customerId + 1; % 顾客节点编号
% 计算到达下一个顾客的距离和时间
distance = distMatrix(currentNode, customerNode);
travelTime = distance / vehicleSpeed;
arrivalTime = max(currentTime + travelTime, customers(customerId, 4)); % 考虑等待时间
% 检查约束
loadOK = (currentLoad + customers(customerId, 3)) <= vehicleCapacity;
timeOK = arrivalTime <= customers(customerId, 5);
if loadOK && timeOK
% 可以添加到当前路径
currentRoute = [currentRoute, customerId];
customerArrivalTimes = [customerArrivalTimes, arrivalTime];
currentLoad = currentLoad + customers(customerId, 3);
currentTime = arrivalTime + customers(customerId, 6); % 加上服务时间
currentNode = customerNode;
else
% 结束当前路径,返回仓库
if ~isempty(currentRoute)
% 计算返回仓库的距离和时间
returnDistance = distMatrix(currentNode, 1);
returnTime = returnDistance / vehicleSpeed;
% 保存当前路径
routes{end+1} = currentRoute;
arrivalTimes{end+1} = customerArrivalTimes;
% 重置当前路径
currentRoute = [];
customerArrivalTimes = [];
currentLoad = 0;
currentTime = 0;
currentNode = 1; % 重置到仓库
% 尝试将当前顾客添加到新路径
i = i - 1; % 重新尝试当前顾客
else
% 即使空路径也无法添加顾客,强制添加(处理不可行解)
currentRoute = [currentRoute, customerId];
customerArrivalTimes = [customerArrivalTimes, arrivalTime];
currentLoad = currentLoad + customers(customerId, 3);
currentTime = arrivalTime + customers(customerId, 6);
currentNode = customerNode;
end
end
end
% 添加最后一条路径
if ~isempty(currentRoute)
routes{end+1} = currentRoute;
arrivalTimes{end+1} = customerArrivalTimes;
end
end
%% 计算适应度函数
function [totalDistance, totalViolations] = calculateFitness(routes, arrivalTimes, customers, depot, distMatrix)
totalDistance = 0;
totalViolations = 0;
for v = 1:length(routes)
route = routes{v};
times = arrivalTimes{v};
if isempty(route)
continue;
end
% 计算从仓库到第一个顾客的距离
totalDistance = totalDistance + distMatrix(1, route(1)+1);
% 计算顾客之间的距离
for i = 1:length(route)-1
totalDistance = totalDistance + distMatrix(route(i)+1, route(i+1)+1);
end
% 计算从最后一个顾客返回仓库的距离
totalDistance = totalDistance + distMatrix(route(end)+1, 1);
% 检查时间窗约束
for i = 1:length(route)
customerId = route(i);
arrivalTime = times(i);
% 检查是否在时间窗内
if arrivalTime > customers(customerId, 5)
totalViolations = totalViolations + 1;
end
end
end
end
%% 锦标赛选择函数
function selectedIds = tournamentSelection(fitness, nSelections, tournamentSize)
popSize = length(fitness);
selectedIds = zeros(nSelections, 1);
for i = 1:nSelections
% 随机选择tournamentSize个个体
candidates = randperm(popSize, tournamentSize);
% 选择适应度最好的
[~, bestIdx] = min(fitness(candidates));
selectedIds(i) = candidates(bestIdx);
end
end
%% 交叉操作函数(顺序交叉OX)
function offspring = crossover(parents, crossoverProb)
nParents = size(parents, 1);
nGenes = size(parents, 2);
offspring = zeros(nParents, nGenes);
for i = 1:2:nParents
if rand < crossoverProb && i+1 <= nParents
parent1 = parents(i, :);
parent2 = parents(i+1, :);
% 选择交叉点
points = sort(randperm(nGenes, 2));
startPoint = points(1);
endPoint = points(2);
% 创建子代
child1 = zeros(1, nGenes);
child2 = zeros(1, nGenes);
% 将父代1的片段复制到子代1
child1(startPoint:endPoint) = parent1(startPoint:endPoint);
% 将父代2的片段复制到子代2
child2(startPoint:endPoint) = parent2(startPoint:endPoint);
% 填充子代1的剩余位置
pointer = 1;
for j = 1:nGenes
if pointer == startPoint
pointer = endPoint + 1;
end
if pointer > nGenes
break;
end
gene = parent2(j);
if ~ismember(gene, child1)
child1(pointer) = gene;
pointer = pointer + 1;
end
end
% 填充子代2的剩余位置
pointer = 1;
for j = 1:nGenes
if pointer == startPoint
pointer = endPoint + 1;
end
if pointer > nGenes
break;
end
gene = parent1(j);
if ~ismember(gene, child2)
child2(pointer) = gene;
pointer = pointer + 1;
end
end
offspring(i, :) = child1;
offspring(i+1, :) = child2;
else
% 不进行交叉,直接复制父代
offspring(i, :) = parents(i, :);
if i+1 <= nParents
offspring(i+1, :) = parents(i+1, :);
end
end
end
end
%% 变异操作函数(交换变异)
function offspring = mutate(offspring, mutationProb)
nIndividuals = size(offspring, 1);
nGenes = size(offspring, 2);
for i = 1:nIndividuals
if rand < mutationProb
% 选择两个不同的位置进行交换
points = randperm(nGenes, 2);
temp = offspring(i, points(1));
offspring(i, points(1)) = offspring(i, points(2));
offspring(i, points(2)) = temp;
end
end
end
说明
这个MATLAB实现解决了带时间窗的车辆路径问题(VRPTW),主要包含以下部分:
1. 问题建模
- 随机生成顾客位置、需求和服务时间窗
- 计算距离矩阵
- 设置车辆容量和速度参数
2. 遗传算法框架
- 初始化:生成随机排列的初始种群
- 评估:解码染色体并计算适应度(总距离+约束违反惩罚)
- 选择:使用锦标赛选择方法
- 交叉:采用顺序交叉(OX)操作符
- 变异:采用交换变异操作符
- 精英保留:保留每代中最优个体
3. 关键函数
initPopulation: 初始化种群evaluatePopulation: 评估种群中每个个体的适应度decodeChromosome: 将染色体解码为车辆路径calculateFitness: 计算路径的总距离和约束违反数tournamentSelection: 锦标赛选择操作crossover: 顺序交叉操作mutate: 交换变异操作
4. 可视化
- 绘制算法收敛曲线
- 可视化最佳路径方案
- 显示详细的路径信息
参考代码 GA_TS_VRP问题遗传算法算路径优化模型 www.youwenfan.com/contentcnh/54867.html
使用
- 直接运行代码即可看到遗传算法求解VRPTW问题的过程
- 可以通过修改参数(如顾客数量、车辆容量、算法参数等)来适应不同的问题规模
- 结果包括收敛曲线图和路径可视化图
这个实现提供了一个完整的遗传算法框架来解决VRPTW问题,可以根据具体需求进行修改和扩展。
浙公网安备 33010602011771号