用遗传算法破解车辆路径规划难题(附 MATLAB 代码解析)

#新星杯·14天创作挑战营·第11期#

在电商爆发式增长的今天,物流配送效率直接决定企业竞争力。如何用最少的车辆、最短的路径、最低的成本完成订单交付?这背后隐藏着一个经典的组合优化问题 ——带时间窗的车辆路径问题(VRPTW)。今天我们就来聊聊如何用遗传算法(GA)攻克这个难题,代码已备好,一起开启智能调度之旅吧~🚀

一、问题建模:还原真实物流场景 📦

1. 场景设定与参数说明

假设我们有一个配送中心(坐标[0,0])和 5 个客户点,需要解决以下核心问题:

  • 车辆限制:最多使用 3 辆货车,每车载重≤100 吨,单次行程≤200 公里
  • 客户需求:每个客户有固定需求量(如客户 1 需要 30 吨货物)
  • 时间窗约束:客户要求货物在特定时间段送达(如客户 2 要求 9-13 点送达)
  • 成本构成:包括车辆启用费、运输费、冷藏费、货损费、时间窗惩罚等

2. 关键参数解读

参数含义示例值
q客户需求量(吨)[30,20,25,15,10]
time_windows时间窗(开始 / 结束时间)[8,12;9,13;…]
K/Q/D最大车辆数 / 载重限制 / 里程限制3/100/200
V1-V4各类成本系数如车辆启用费 200 元 / 辆
d距离矩阵(配送中心与客户间距离)squareform(pdist)生成

这些参数就像拼图的碎片🧩,只有准确建模,才能拼出最优解~

二、遗传算法:模拟自然进化的优化引擎 🌿

1. 染色体编码:用客户顺序代表配送路径

我们用排列编码表示染色体:每个染色体是一个客户顺序的排列(如[2,5,3,1,4]代表配送顺序为客户 2→5→3→1→4)。这种编码方式直观反映路径顺序,便于交叉和变异操作~

% 初始化种群:生成100条随机客户顺序
population = zeros(pop_size, num_customers);
for i = 1:pop_size
    population(i,:) = randperm(num_customers); % randperm生成随机排列
end

2. 适应度计算:解码染色体为实际路径

decode_chromosome函数是算法的 “翻译官”,负责将客户顺序转换为具体的车辆路径,并计算总成本。核心逻辑如下:

  1. 路径分割:按载重和里程限制将客户分配到不同车辆
  2. 成本计算
    • C1:车辆启用费(用几辆车花多少钱🚗)
    • C2:运输成本(里程 × 单价,跑越远越贵🏎️)
    • C3:冷藏成本(运输时间 + 卸货时间,生鲜配送必备🧊)
    • C4:货损成本(时间越长损耗越高,水果运输痛点🍎)
    • C5:时间窗惩罚(早到 / 迟到都要扣钱,准时是王道⏰)
% 计算时间窗惩罚示例
t_arrive = cust_times(j); % 到达时间
te = time_windows(j,1); tl = time_windows(j,2);
if t_arrive < te, C5 += V3*(te - t_arrive); % 早到罚等待费
elseif t_arrive > tl, C5 += V4*(t_arrive - tl); % 迟到罚违约金
end

3. 遗传操作:模拟生物进化的三大法宝

① 锦标赛选择(Tournament Selection)
  • 规则:每次从种群中随机选 3 个个体,选成本最低的作为 “父母”👨👩
  • 作用:避免优秀个体被淘汰,类似选秀节目中的 “晋级保护机制”✨
② OX 交叉(Order Crossover)
  • 步骤
    1. 随机选择两个切点,保留父代 1 的中间段(如客户 3→5)
    2. 按父代 2 的顺序填充剩余客户,保持相对顺序不变
  • 效果:继承父代优质路径结构,类似 “基因重组”🧬
% OX交叉示例:父代1=[2,5,3,1,4],父代2=[4,1,5,3,2]
% 切点选2和4,中间段为5,3,1
% 子代1=剩余客户按父代2顺序填充:4, [5,3,1], 2 → [4,5,3,1,2]
③ 交换变异(Swap Mutation)
  • 操作:随机交换染色体中两个客户的位置(如客户 2 和 4 互换)
  • 意义:增加种群多样性,避免陷入局部最优,像给算法 “打补丁”🔄

三、实战运行:从随机搜索到最优路径 🛣️

1. 算法迭代过程

我们设定种群大小 100,迭代 200 次,算法会在每次迭代中输出当前最优成本:

2. 最优路径结果

解码最优染色体后,得到 3 条车辆路径:

路径特点:

  • 载重均衡:每辆车载重均≤100 吨,避免浪费
  • 时间窗合规:通过cust_times计算,所有客户到达时间均在时间窗内✅
  • 成本最优:综合考虑各类成本,实现总费用最小化

四、调参技巧与常见问题 🛠️

1. 参数调整策略

参数作用调优建议
pop_size种群规模太小易早熟,太大耗时长,建议 50-200
crossover_rate交叉概率过高破坏优质个体,过低降低进化速度,建议 0.7-0.9
mutation_rate变异概率过低缺乏创新,过高变 “随机搜索”,建议 0.01-0.2
max_gen最大迭代次数观察收敛曲线,稳定后可提前终止

2. 约束处理技巧

  • 硬约束(载重 / 里程 / 车辆数):违反则设为无穷大成本(total_cost = Inf),强制淘汰无效解❌
  • 软约束(时间窗):用惩罚函数转化为成本,允许一定程度违反,平衡解的可行性与优化性⚖️

3. 算法改进方向

  • 混合算法:结合局部搜索(如 2-opt)提升解质量,类似 “遗传算法 + 精装修”🏠
  • 动态建模:考虑实时交通数据、客户临时需求,开发在线调度系统📱
  • 多目标优化:同时优化成本、碳排放、客户满意度,生成帕累托前沿解集🌍

五、总结:遗传算法的物流应用启示 📚

通过这个案例,我们看到遗传算法在解决复杂组合优化问题时的强大能力:

  1. 灵活性:适用于多种约束条件(时间窗、载重、里程),适配不同物流场景
  2. 可扩展性:轻松扩展到上百个客户点,只需调整参数即可应对大规模问题
  3. 工程价值:相比人工调度,可降低成本 10%-30%,显著提升企业效率💼

未来,随着物联网(IoT)和自动驾驶技术的发展,车辆路径优化将与实时数据深度融合。想象一下:货车通过传感器实时获取路况,算法动态调整路径,真正实现 “智慧物流”~🚛💨

动手实践:试着将客户数增加到 20 个,调整num_customerslocations,观察算法收敛速度变化~欢迎在评论区分享你的实验结果!👇

延伸阅读

  • 车辆路径问题经典文献:《The Vehicle Routing Problem》
  • MATLAB 优化工具箱:ga函数官方文档
  • 物流优化案例:DHL 如何用算法降低最后一公里成本🚀

完整代码(省流版)

%% 参数初始化
data = readmatrix("模糊.xlsx");
data(:, 1) = []; data(2: 3, :) = []; data(5, :) = [];

locations = data(:, 1: 2);
num_customers = length(locations) - 1;
aq_fuzzy = data(2: end, 3: 5);
w = [1 / 6, 4 / 6, 1 / 6];
q = sum(aq_fuzzy.*w,2)';

load time_window
time_windows = [te, tl]; % 随机生成两组向量
K = 5; % 最大车辆数
Q = 3; % 车辆载重限制
D = 500; % 车辆里程限制
speed = 20; % 车速(km/h)
t_per_unit = 1; % 卸货时间/单位需求

% 成本参数
V1 = 100; V2 = 1; P1 = 0.2; P2 = 0.5;
alpha1 = 0.01; alpha2 = 0.02; P = 10;  % 货损系数与单价
V3 = 2; V4 = 5;

% 经纬网上任意两点间距离(km)
locations=locations.*pi/180;
m=size(locations,1);
for i=1:m
    for j=1:m
        h(i,j)=acos(cos(locations(i,2))*cos(locations(j,2))*cos(locations(i,1)-locations(j,1))+sin(locations(i,2))*sin(locations(j,2)));
        d=6378.137.*h;
    end
end

% 遗传算法参数
pop_size = 50; max_gen = 200;
crossover_rate = 0.8; mutation_rate = 0.1;

%% 初始化种群
population = zeros(pop_size, num_customers);
for i = 1:pop_size
    population(i,:) = randperm(num_customers);
end

%% 遗传算法主循环
best_cost = Inf; best_chrom = [];
cost_history = zeros(max_gen, 1);

for gen = 1:max_gen
    % 计算适应度
    costs = zeros(pop_size, 1);
    for i = 1:pop_size
        [costs(i), comps, ~] = decode_chromosome(population(i,:), d, q, time_windows, ...
            K, Q, D, speed, t_per_unit, V1, V2, P1, P2, alpha1, alpha2, P, V3, V4);
        if costs(i) < best_cost
            best_cost = costs(i);
            best_chrom = population(i,:);
            best_components = comps;
        end
    end
    cost_history(gen) = best_cost;
    
    % 锦标赛选择
    parents = zeros(pop_size, num_customers);
    for i = 1:pop_size
        candidates = randperm(pop_size, 3);
        [~, idx] = min(costs(candidates));
        parents(i,:) = population(candidates(idx),:);
    end
    
    % OX交叉
    offspring = [];
    for i = 1:2:pop_size
        p1 = parents(i,:); p2 = parents(i+1,:);
        if rand < crossover_rate
            [c1, c2] = ox_crossover(p1, p2);
        else
            c1 = p1; c2 = p2;
        end
        offspring = [offspring; c1; c2];
    end
    
    % 交换变异
    for i = 1:pop_size
        if rand < mutation_rate
            pos = randperm(num_customers, 2);
            offspring(i, pos) = offspring(i, fliplr(pos));
        end
    end
    
    population = offspring;
    
    fprintf('Generation %d: Best Cost = %.2f\n', gen, best_cost);
end

%% 结果展示
[~, comps, routes] = decode_chromosome(best_chrom, d, q, time_windows, ...
    K, Q, D, speed, t_per_unit, V1, V2, P1, P2, alpha1, alpha2, P, V3, V4);

fprintf('最优总成本: %.2f\n', best_cost);
fprintf('  车辆固定成本 C1: %.2f\n', comps(1));
fprintf('  运输里程成本 C2: %.2f\n', comps(2));
fprintf('  冷藏成本 C3: %.2f\n', comps(3));
fprintf('  货损成本 C4: %.2f\n', comps(4));
fprintf('  时间窗惩罚 C5: %.2f\n', comps(5));

disp('最优路径:');
for k = 1:length(routes)
    if ~isempty(routes{k})
        fprintf('车辆 %d: %s\n', k, mat2str(routes{k} + 1));
    end
end

% 绘制路线图
% 使用 cellfun 和 isempty 检测空元素
emptyIndices = cellfun(@isempty, routes);
% 使用逻辑索引删除空元素
routes(emptyIndices) = [];

figure; hold on;
plot(locations(1,1),locations(1,2),'rs','MarkerSize',8,'DisplayName','配送中心');
plot(locations(2:end,1),locations(2:end,2),'bo','DisplayName','客户点');
colors = lines(numel(routes));
for i = 1:numel(routes)
    route = routes{i}; xy = locations(route + 1,:);
    plot(xy(:,1), xy(:,2),'-','LineWidth',1.5,'Color',colors(i,:), 'DisplayName',sprintf('车%d',i));
end
xlabel('X 坐标'); ylabel('Y 坐标'); title('最优配送路径'); legend('show'); hold off;

% 收敛曲线
figure;
plot(cost_history);
xlabel('迭代次数'); ylabel('总成本');
title('收敛曲线');


function [total_cost, comps, routes] = decode_chromosome(chrom, d, q, time_windows, ...
    K, Q, D, speed, t_per_unit, V1, V2, P1, P2, alpha1, alpha2, P, V3, V4)
    routes = cell(1, K); current_route = 0; current_load = 0; current_dist = 0;
    used_vehicles = 1; cust_times = zeros(1, length(q));
% 分配客户到路径
for i = 1:length(chrom)
    cust = chrom(i);
    new_load = current_load + q(cust);
    temp_route = [current_route, cust, 0];
    
    % 计算临时距离
    temp_dist = 0;
    for j = 1:length(temp_route)-1
        temp_dist = temp_dist + d(temp_route(j)+1, temp_route(j+1)+1);
    end
    
    if new_load <= Q && temp_dist <= D
        current_route = [current_route, cust];
        current_load = new_load;
        current_dist = temp_dist;
    else
        % 结束当前车辆路径
        routes{used_vehicles} = [current_route, 0];
        used_vehicles = used_vehicles + 1;
        if used_vehicles > K
            total_cost = Inf; return; 
        end
        current_route = [0, cust];
        current_load = q(cust);
        current_dist = d(1, cust+1) + d(cust+1,1);
    end
end
routes{used_vehicles} = [current_route, 0];
% 计算各项成本
C1 = used_vehicles * V1;
C2 = 0; C3 = 0; C4 = 0; C5 = 0;

for k = 1:used_vehicles
    route = routes{k};
    if length(route) < 3, continue; end
    
    % 运输成本
    dist = sum(d(sub2ind(size(d), route(1:end-1)+1, route(2:end)+1)));
    C2 = C2 + dist* V2;
    
    % 冷藏成本
    transport_time = dist / speed;
    unload_time = sum(q(route(2:end-1))) * t_per_unit;
    C3 = C3 + P1*transport_time + P2*unload_time;
    
    % 货损成本
    time = 0;
    for j = 2:length(route)-1
        from = route(j-1); to = route(j);
        time = time + d(from+1, to+1)/speed;
        cust_times(to) = time;
        unload = q(to)*t_per_unit;
        A = alpha1*time; B = alpha2*unload;
        C4 = C4 + (1-exp(-A) + 1-exp(-B)) * P*q(to);
        time = time + unload;
    end
end

% 时间窗惩罚
for j = 1:length(q)
    t_arrive = cust_times(j);
    [te, tl] = deal(time_windows(j,1), time_windows(j,2));
    if t_arrive < te
        C5 = C5 + V3*(te - t_arrive);
    elseif t_arrive > tl
        C5 = C5 + V4*(t_arrive - tl);
    end
end

total_cost = C1 + C2 + C3 + C4 + C5;
    comps = [C1, C2, C3, C4, C5];
end

%% OX交叉函数
function [c1, c2] = ox_crossover(p1, p2)
    n = length(p1);
    cut = sort(randperm(n, 2));
    segment = p1(cut(1):cut(2));
    
    % 生成子代1
    remain = p2(~ismember(p2, segment));
    c1 = [remain(1:cut(1)-1), segment, remain(cut(1):end)];
    
    % 生成子代2
    segment = p2(cut(1):cut(2));
    remain = p1(~ismember(p1, segment));
    c2 = [remain(1:cut(1)-1), segment, remain(cut(1):end)];
end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值