使用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

使用

  1. 直接运行代码即可看到遗传算法求解VRPTW问题的过程
  2. 可以通过修改参数(如顾客数量、车辆容量、算法参数等)来适应不同的问题规模
  3. 结果包括收敛曲线图和路径可视化图

这个实现提供了一个完整的遗传算法框架来解决VRPTW问题,可以根据具体需求进行修改和扩展。

posted @ 2025-09-17 09:31  w199899899  阅读(82)  评论(0)    收藏  举报