第一次接触旅行商问题(TSP)是在研究生时期的运筹学课上,当时就被这个看似简单实则复杂的问题深深吸引。想象你是一位快递员,需要访问城市中的多个配送点,如何规划路线才能走最短的路程?这就是TSP问题的现实写照。随着城市数量增加,可能的路线组合呈阶乘级增长,10个城市就有362880种可能路线,完全穷举根本不现实。
这时候就需要邻域搜索算法这种启发式方法。2opt作为其中最经典的算法之一,原理简单却效果惊人。它的核心思想就像玩拼图:随机选择两个边进行交换,如果总距离变短就保留这个改动。我曾在物流项目中用这个算法将配送路线缩短了23%,客户非常满意。
MATLAB作为工程计算神器,特别适合实现这类算法。它的矩阵运算能力能极大简化距离计算,可视化功能还能直观展示优化过程。下面这段代码可以快速计算路线长度:
matlab复制function total_dist = calc_route_dist(dist_matrix, route)
n = length(route);
total_dist = sum(dist_matrix(sub2ind(size(dist_matrix),...
route(1:n-1), route(2:n))));
total_dist = total_dist + dist_matrix(route(end), route(1));
end
在动手写代码前,我们需要理清2opt的完整流程。就像盖房子要先打地基,算法实现也要先构建框架。我的经验是分三步走:
这个框架我用在了多个项目中,稳定性很好。对应的MATLAB主程序结构如下:
matlab复制%% 参数设置
max_iter = 1000; % 最大迭代次数
max_no_improve = 50; % 连续无改进次数阈值
%% 初始化
dist_mat = init_dist_matrix(); % 距离矩阵
current_route = randperm(num_cities); % 随机初始解
best_route = current_route;
best_dist = calc_route_dist(dist_mat, best_route);
%% 主循环
for iter = 1:max_iter
% 邻域生成与评估
[new_route, new_dist] = generate_2opt_neighbor(...);
% 解更新
if new_dist < best_dist
best_route = new_route;
best_dist = new_dist;
no_improve = 0;
else
no_improve = no_improve + 1;
end
% 终止判断
if no_improve >= max_no_improve
break;
end
end
邻域生成是2opt的核心魔法。不同于简单随机交换,系统性的邻域搜索要遍历所有可能的边交换组合。这就像在迷宫中不盲目乱撞,而是有条理地检查每条岔路。
在MATLAB中,我常用nchoosek函数高效生成所有两点组合:
matlab复制function neighbors = gen_2opt_neighbors(route)
n = length(route);
swap_pairs = nchoosek(2:n, 2); % 生成所有交换位置组合
neighbors = zeros(size(swap_pairs,1), n);
for i = 1:size(swap_pairs,1)
new_route = route;
% 执行2opt交换
new_route(swap_pairs(i,1):swap_pairs(i,2)) = ...
fliplr(new_route(swap_pairs(i,1):swap_pairs(i,2)));
neighbors(i,:) = new_route;
end
end
实测发现,对于20个城市的问题,这种实现方式比for循环嵌套快3倍以上。不过要注意内存消耗,城市过多时需要分批次处理。
第一次实现2opt时,完整跑完50个城市的问题花了近10分钟,完全不能忍。经过多次优化,现在同样规模只需15秒。关键技巧包括:
这里分享一个超实用的距离更新技巧。常规做法是每次重新计算整条路线距离,其实只需要计算变化部分:
matlab复制% 传统方法
new_dist = calc_route_dist(dist_mat, new_route);
% 优化方法(假设交换了i到j段)
delta = dist_mat(route(i-1),route(j)) + dist_mat(route(i),route(j+1))...
- (dist_mat(route(i-1),route(i)) + dist_mat(route(j),route(j+1)));
new_dist = current_dist + delta;
MATLAB的强大可视化能帮我们直观理解算法行为。我习惯在迭代过程中绘制这些图形:
这段代码可以生成动态路线图:
matlab复制h = plot(cities(:,1), cities(:,2), 'o');
hold on;
route_plot = plot(cities(best_route([1:end 1]),1),...
cities(best_route([1:end 1]),2), 'r-');
for iter = 1:max_iter
% ...算法迭代过程...
% 更新图形
set(route_plot, 'XData', cities(best_route([1:end 1]),1),...
'YData', cities(best_route([1:end 1]),2));
title(sprintf('Iter: %d, Dist: %.2f', iter, best_dist));
drawnow;
end
在实际项目中踩过不少坑,这里总结三个最典型的:
局部最优陷阱:算法过早收敛到次优解。我的应对方法是引入随机重启机制,当检测到停滞时,给当前解一个随机扰动。
大规模数据内存溢出:城市数超过100时,完整邻域集会消耗大量内存。改用增量生成策略,每次只生成部分邻域解。
非对称距离矩阵:有些项目中的距离不是对称的(如单行道)。需要修改邻域生成逻辑,确保方向正确。
为了验证2opt的效果,我做了组对比实验(运行环境:i7-11800H, 32GB RAM):
| 城市数量 | 随机解距离 | 2opt优化距离 | 优化率 | 耗时(秒) |
|---|---|---|---|---|
| 20 | 543.2 | 387.6 | 28.6% | 0.8 |
| 50 | 1287.4 | 892.3 | 30.7% | 15.2 |
| 100 | 2534.1 | 1786.5 | 29.5% | 182.4 |
可以看到,2opt能稳定减少约30%的路线距离。虽然随着规模增大耗时增加,但在实际应用中,50个节点以内的场景完全能满足实时性要求。
最后分享一个项目中的真实案例:为某连锁超市设计配送路线时,将2opt与聚类算法结合,先分区再优化,使总配送时间缩短了35%,每年节省燃油成本超百万元。这让我深刻体会到,好的算法实现真能创造实实在在的商业价值。