自适应大邻域搜索(ALNS)算法的MATLAB 实现

一、 VRPTW 核心数学模型

假设有 NNN 个客户,KKK 辆相同的容量为 QQQ 的车辆, depot(仓库)编号为 000。客户 iii 的需求量为 qiq_iqi,服务时间为 sis_isi,时间窗为 [ei,li][e_i, l_i][ei,li],车辆从 depot 出发的时间为 000。

决策变量:

  • xijk∈{0,1}x_{ijk} \in \{0, 1\}xijk∈{0,1}:车辆 kkk 是否从客户 iii 行驶到客户 jjj。
  • tikt_{ik}tik:车辆 kkk 到达客户 iii 的时间。

目标函数(最小化总行驶成本/时间):
min⁡Z=∑k∈K∑i∈V∑j∈Vcijxijk \min Z = \sum_{k \in K} \sum_{i \in V} \sum_{j \in V} c_{ij} x_{ijk} minZ=k∈K∑i∈V∑j∈V∑cijxijk

核心约束条件:

  1. 流量守恒(每个客户被访问一次): ∑k∈K∑j∈Vxijk=1,∀i∈N\sum_{k \in K} \sum_{j \in V} x_{ijk} = 1, \quad \forall i \in N∑k∈K∑j∈Vxijk=1,∀i∈N
  2. 车辆容量约束: ∑i∈Nqi∑j∈Vxijk≤Q,∀k∈K\sum_{i \in N} q_i \sum_{j \in V} x_{ijk} \le Q, \quad \forall k \in K∑i∈Nqi∑j∈Vxijk≤Q,∀k∈K
  3. 时间窗约束(硬约束): ei≤tik≤li,∀i∈N,k∈Ke_i \le t_{ik} \le l_i, \quad \forall i \in N, k \in Kei≤tik≤li,∀i∈N,k∈K
  4. 时间递推关系: tjk≥tik+si+τij−M(1−xijk),∀i,j∈V,k∈Kt_{jk} \ge t_{ik} + s_i + \tau_{ij} - M(1 - x_{ijk}), \quad \forall i,j \in V, k \in Ktjk≥tik+si+τij−M(1−xijk),∀i,j∈V,k∈K (τij\tau_{ij}τij为行驶时间,MMM为大常数)

二、 MATLAB 工程级 ALNS 算法框架

自适应大邻域搜索(ALNS)通过**多种破坏(Remove)修复(Insert)**算子的组合,在庞大解空间中高效寻优。算法会根据算子的历史表现动态调整选择它们的主观概率。

以下是完整的 MATLAB 代码,包含数据读取、解的解码、目标计算、算子逻辑及可视化:

matlab 复制代码
%% VRPTW - 自适应大邻域搜索算法 (ALNS) 完整框架
% 特点: 面向对象编程风格, 模块化设计, 适合二次开发与论文复现

clear; clc; close all;

%% 1. 数据加载与初始化
data = read_VRPTW_data('solomon_25.txt'); % 读取数据函数 (需自备或见下方辅助函数)
num_customers = data.num_customers;
num_vehicles = data.num_vehicles;
capacity = data.capacity;

% 算法参数
max_iter = 5000;
max_no_improve = 500;
destroy_ops = {'random_remove', 'worst_remove'};
repair_ops = {'greedy_insert', 'regret_insert'};
% 算子权重与得分 (用于自适应选择)
op_weights = ones(length(destroy_ops) + length(repair_ops), 1);
op_scores = zeros(size(op_weights));

% 初始化: 构造一个初始可行解 (这里使用简单的贪婪插入)
current_solution = greedy_initialization(data, num_vehicles, capacity);
best_solution = current_solution;

fprintf('初始解目标值: %.2f\n', evaluate_solution(current_solution, data));

%% 2. 主循环 - ALNS 搜索
iter = 0;
no_improve_count = 0;

while iter < max_iter && no_improve_count < max_no_improve
    iter = iter + 1;
    
    % --- 步骤1: 自适应选择算子 ---
    total_weight = sum(op_weights);
    prob = op_weights / total_weight;
    chosen_op_idx = randsample(length(op_weights), 1, true, prob);
    
    destroy_op = destroy_ops{mod(chosen_op_idx-1, length(destroy_ops)) + 1};
    repair_op = repair_ops{ceil(chosen_op_idx / length(destroy_ops))};
    
    % --- 步骤2: 执行破坏与修复 ---
    % 深拷贝当前解以避免直接修改
    temp_solution = copy_solution(current_solution);
    
    % 破坏: 随机移除一部分客户
    [temp_solution, removed_customers] = feval(destroy_op, temp_solution, data, 5); % 移除5个
    
    % 修复: 将移除的客户重新插入
    temp_solution = feval(repair_op, temp_solution, removed_customers, data, capacity);
    
    % --- 步骤3: 模拟退火接受准则 ---
    current_cost = evaluate_solution(current_solution, data);
    new_cost = evaluate_solution(temp_solution, data);
    delta = new_cost - current_cost;
    
    T = 100 * 0.99^(iter); % 简化的温度衰减
    
    if delta < 0 || rand() < exp(-delta / T)
        current_solution = temp_solution;
        
        % 更新最佳解
        best_cost_current = evaluate_solution(best_solution, data);
        if new_cost < best_cost_current
            best_solution = temp_solution;
            no_improve_count = 0;
            op_scores(chosen_op_idx) = op_scores(chosen_op_idx) + 1.5; % 找到更优解奖励
        else
            op_scores(chosen_op_idx) = op_scores(chosen_op_idx) + 0.5; % 接受劣解奖励
        end
    else
        op_scores(chosen_op_idx) = op_scores(chosen_op_idx) - 1; % 拒绝惩罚
    end
    
    no_improve_count = no_improve_count + 1;
    
    % --- 步骤4: 更新算子权重 (每100次迭代) ---
    if mod(iter, 100) == 0
        op_weights = 0.8 * op_weights + 0.2 * op_scores;
        op_scores = zeros(size(op_weights));
        fprintf('Iter: %d, Best Cost: %.2f, Current Cost: %.2f\n', ...
            iter, evaluate_solution(best_solution, data), current_cost);
    end
end

%% 3. 结果输出与可视化
fprintf('=========================================\n');
fprintf('最终最优目标值: %.2f\n', evaluate_solution(best_solution, data));
visualize_routes(best_solution, data);

%% =========================================================
%% 辅助函数 1: 计算路径总代价 (距离 + 时间窗惩罚)
function cost = evaluate_solution(solution, data)
    routes = solution.routes;
    cost = 0;
    for v = 1:length(routes)
        if isempty(routes{v})
            continue;
        end
        route = [0, routes{v}, 0]; % 加上depot
        for i = 1:(length(route)-1)
            from = route(i);
            to = route(i+1);
            cost = cost + data.dist_matrix(from+1, to+1); % MATLAB索引从1开始
        end
    end
end

%% 辅助函数 2: 贪初始化 (Greedy Insertion)
function solution = greedy_initialization(data, num_vehicles, capacity)
    unassigned = 1:data.num_customers;
    routes = cell(1, num_vehicles);
    current_vehicle = 1;
    current_load = 0;
    current_time = 0;
    
    while ~isempty(unassigned)
        best_cust = -1;
        min_increase = inf;
        
        for i = 1:length(unassigned)
            cust = unassigned(i);
            cust_demand = data.demands(cust);
            if current_load + cust_demand <= capacity
                % 检查时间窗可行性 (简略版)
                travel_time = data.dist_matrix(end, cust+1); % 从depot出发
                arrival = current_time + travel_time;
                wait_time = max(0, data.time_windows(cust, 1) - arrival);
                departure = max(arrival, data.time_windows(cust, 1)) + data.service_times(cust);
                
                if departure <= data.time_windows(cust, 2)
                    cost_increase = data.dist_matrix(end, cust+1) + data.dist_matrix(cust+1, end);
                    if cost_increase < min_increase
                        min_increase = cost_increase;
                        best_cust = cust;
                    end
                end
            end
        end
        
        if best_cust ~= -1
            routes{current_vehicle} = [routes{current_vehicle}, best_cust];
            current_load = current_load + data.demands(best_cust);
            travel_time = data.dist_matrix(end, best_cust+1);
            current_time = max(current_time + travel_time, data.time_windows(best_cust,1)) + data.service_times(best_cust);
            unassigned(unassigned == best_cust) = [];
        else
            current_vehicle = current_vehicle + 1;
            current_load = 0;
            current_time = 0;
            if current_vehicle > num_vehicles
                break;
            end
        end
    end
    solution.routes = routes;
end

%% 辅助函数 3: 破坏算子 - 随机移除
function [solution, removed] = random_remove(solution, data, num_remove)
    all_nodes = vertcat(solution.routes{:});
    if length(all_nodes) < num_remove
        num_remove = length(all_nodes);
    end
    idx = randperm(length(all_nodes), num_remove);
    removed = all_nodes(idx);
    
    for v = 1:length(solution.routes)
        solution.routes{v}(ismember(solution.routes{v}, removed)) = [];
    end
end

%% 辅助函数 4: 修复算子 - 贪婪插入
function solution = greedy_insert(solution, customers, data, capacity)
    while ~isempty(customers)
        best_route = -1;
        best_pos = -1;
        best_cust = -1;
        min_cost = inf;
        
        for c = 1:length(customers)
            cust = customers(c);
            for v = 1:length(solution.routes)
                route = solution.routes{v};
                for p = 0:length(route)
                    % 检查容量
                    if sum(data.demands([route(1:p), cust, route(p+1:end)])) > capacity
                        continue;
                    end
                    % 检查时间窗 (简略伪代码, 实际应用需完整CVRPTW校验)
                    % 计算插入代价
                    cost = insertion_cost(route, p, cust, data);
                    if cost < min_cost
                        min_cost = cost;
                        best_route = v;
                        best_pos = p;
                        best_cust = cust;
                    end
                end
            end
        end
        
        if best_cust ~= -1
            route = solution.routes{best_route};
            solution.routes{best_route} = [route(1:best_pos), best_cust, route(best_pos+1:end)];
            customers(customers == best_cust) = [];
        else
            break; % 无法插入剩余客户
        end
    end
end

%% 辅助函数 5: 计算插入代价 (简化版)
function cost = insertion_cost(route, pos, cust, data)
    if isempty(route)
        cost = data.dist_matrix(end, cust+1) + data.dist_matrix(cust+1, end);
        return;
    end
    prev = route(pos);
    next = route(min(pos+1, length(route)));
    % 增加的距离
    added_dist = data.dist_matrix(prev+1, cust+1) + data.dist_matrix(cust+1, next+1) - data.dist_matrix(prev+1, next+1);
    cost = added_dist;
end

%% 辅助函数 6: 复制解结构体
function new_sol = copy_solution(old_sol)
    new_sol = struct();
    new_sol.routes = cell(size(old_sol.routes));
    for i = 1:length(old_sol.routes)
        new_sol.routes{i} = old_sol.routes{i};
    end
end

%% 辅助函数 7: 可视化路径
function visualize_routes(solution, data)
    figure('Name', 'VRPTW 路径规划结果', 'Color', 'w', 'Position', [100 100 800 600]);
    hold on; grid on; axis equal;
    colors = lines(length(solution.routes));
    
    % 绘制客户点
    scatter(data.coords(1:end-1, 1), data.coords(1:end-1, 2), 50, 'b', 'filled');
    text(data.coords(1:end-1, 1), data.coords(1:end-1, 2), string(1:data.num_customers), 'FontSize', 8);
    % 绘制depot
    scatter(data.coords(end, 1), data.coords(end, 2), 100, 'r', 'p', 'filled');
    text(data.coords(end, 1), data.coords(end, 2), 'Depot', 'FontSize', 10);
    
    % 绘制路径
    for v = 1:length(solution.routes)
        if isempty(solution.routes{v})
            continue;
        end
        route = [0, solution.routes{v}, 0];
        for i = 1:(length(route)-1)
            from = route(i);
            to = route(i+1);
            plot([data.coords(from+1, 1), data.coords(to+1, 1)], ...
                 [data.coords(from+1, 2), data.coords(to+1, 2)], ...
                 'Color', colors(v,:), 'LineWidth', 1.5);
        end
    end
    title('VRPTW 最优路径规划图');
end

%% 辅助函数 8: 读取VRPTW数据 (简化示例, 实际需根据数据格式调整)
function data = read_VRPTW_data(filename)
    % 这里使用示例数据代替文件读取
    data.num_customers = 25;
    data.num_vehicles = 5;
    data.capacity = 100;
    rng(42);
    % 随机生成坐标 (最后一个为depot)
    data.coords = [rand(25,2)*100; 50, 50]; 
    % 计算距离矩阵
    data.dist_matrix = squareform(pdist(data.coords, 'euclidean'));
    % 需求量
    data.demands = [randi([1, 20], 25, 1); 0];
    % 时间窗 (早时间, 晚时间)
    data.time_windows = [randi([0, 50], 25, 2), 100*ones(25,1)]; 
    data.time_windows(:,2) = data.time_windows(:,1) + data.time_windows(:,2);
    data.time_windows = [data.time_windows; 0, 1000, 0]; % depot时间窗
    % 服务时间
    data.service_times = [randi([5, 15], 25, 1); 0];
end

参考代码 带时间窗的vrp问题 www.youwenfan.com/contentcsu/64885.html

三、 工程实战建议与扩展方向

代码实现了了一个标准的 ALNS 骨架。在真实的科研或工程应用中,您可以基于这个框架进行以下高价值扩展:

  1. 增强算子库
    • 破坏算子 :增加 worst_remove(移除导致目标函数恶化最多的客户)、related_remove(基于距离或时间相似性移除一组客户)。
    • 修复算子 :实现 regret_insert(计算客户在所有可能插入位置的最小代价与次小代价之差,优先插入差值大的客户,即 regret-2 或 regret-3 策略)。
  2. 精细化约束校验
    insertion_cost 函数中,目前的代码为了简化只计算了距离。您需要补全基于递推公式的时间窗硬约束校验。如果插入导致时间窗违约,直接返回一个极大的代价(如 inf)。
  3. 大规模问题加速
    当客户点超过 100 个时,双重循环的寻找最佳插入位置会成为瓶颈。可以通过 Candidate List(候选列表) 策略,仅为每个客户保留距离最近的 LLL 个邻居进行评估,从而将复杂度从 O(N3)O(N^3)O(N3) 降至 O(N2log⁡N)O(N^2 \log N)O(N2logN)。
相关推荐
RH2312113 小时前
2026.4.29数据结构 直接插入排序&&希尔排序
数据结构·算法·排序算法
沐知全栈开发3 小时前
API 类别 - 实用工具
开发语言
Cx330❀3 小时前
Qt 入门指南:从零搭建开发环境到第一个图形界面程序
xml·大数据·开发语言·网络·c++·人工智能·qt
SilentSamsara3 小时前
装饰器基础:从闭包到装饰器的自然演变
开发语言·前端·vscode·python·青少年编程·pycharm
搬砖的小码农_Sky3 小时前
AI Agent:OpenClaw的算法架构
人工智能·算法·ai·架构·人机交互·agi
热心网友俣先生3 小时前
2026年金地杯A题解题思路
算法
科研前沿3 小时前
SpaceOS™空间计算底座与五大自研引擎,实现多项关键技术突破
大数据·运维·人工智能·算法·重构
今天长肉了吗3 小时前
风控指标平台实战:大数据量下如何设计分批处理
开发语言·数据库·python
昵称小白3 小时前
C++ 刷题语法速查
c++·算法