遗传算法解决旅行商问题

news2024/11/29 13:42:16

问题描述

旅行商问题(TSP). 一个商人欲从自己所在的城市出发,到若干个城市推销商品,然后回到其所在的城市。如何选择一条周游路线,使得商人经过每个城市一次且仅一次后回到起点,并使他所走过的路径最短?

TSP 即Travelling Salesman Problem. 中文翻译过来就是旅行商问题。

旅行商问题是一个典型的NP难问题。NP指的是Non-deterministic Polynomial,即多项式复杂程度的非确定性问题。由于该问题的组合特性,旅行商问题已成为测试新算法的标准问题,如模拟退火、神经网络和演化算法等都用旅行商问题作为测试用例。旅行商问题的一个实例可由一个距离矩阵所给定。

用遗传算法求解旅行商问题时,适应函数可以取为目标函数或目标函数的一个简单变换,选择策略可以是轮盘赌选择,所以算法设计的重点主要集中在以下三个方面:

  1. 采用适当的方法对周游路线编码

  1. 设计专门的遗传算子,以避免不可行性

  1. 防止过早收敛

下面讨论周游路线常用的几种表示及其相应的遗传算子。

周游路线编码

主要有3种编码表示

  1. 近邻表示

  1. 次序表示

  1. 路径表示

近邻表示

近邻表示将一条周游路线表示成n个城市 的一个排列Π=(Π1,Π2,...,Πn)Πi=j当且仅当周游路线中从城市i到达的下一个城市为j.

例如,排列

(2 4 8 3 9 7 1 5 6)

表示周游路线为1−2−4−3−8−5−9−6−7−1.

显然,每一条周游路线都对应一个近邻表示,但任一近邻排列却不一定对应于一条周游路线。

例如,排列

(2 4 8 1 9 3 5 7 6)

导致了不完全回路1−2−4−1,因而无法对应一条周游路线。所以周游路线的近邻表示就没有必要再往下介绍。

次序表示

次序表示将一条周游路线表示为n个城市的有序表,其中,表中的第i个元素在1n−i+1取值。该表的一个优点是每一个次序表示都对应于一条合法的周游路线。

次序表示的基本思想是:取n个城市的某个排列(Π1,Π2,...,Πn)作为参照排列,通常取(1,2,...,n)作为参照排列,然后将周游路线中的城市按照其在参照排列中的次序记录下来,形成一个具有n个元素的有序表.具体做法如下:

对给定的一条路径Π1−Π2−...−Πn,首先记录城市Π1在参照排列(p1,p2,...,pn)中的次序i1,将Π1(p1,p2,...,pn)中删除得n−1个城市的参照排列(q1,q2,...,qk),k=n−1,再记录城市Π2在参照排列(q1,q2,...,qk),k=n−1中的次序i2,然后将城市Π2(q1,q2,...,qk),k=n−1中删除得n−2个城市的参照排列(r1,r2,...,rk),k=n−2,以此类推,直到城市Πn的次序in记录下来为止。有序表(i1,i2,...,in)称为路径Π1−Π2−...−Πn的次序表示。注意ij的取值范围为1≤ij≤n−j+1.

例如,若取(1,2,...,9)作为参照排列,那么路径

1−2−4−3−8−5−9−6−7

的次序表示为

(1 1 2 1 4 1 3 1 1).

虽然次序表示具有可以使用传统杂交算子的优点,但实验表明在次序表示与传统的杂交算子相结合所得到的结果并不理想。

路径表示

路径表示也许是周游路径最自然的一种表示。例如,一条周游路线

5−1−7−8−9−4−6−2−3−5

可表示为排列(5 1 7 8 9 4 6 2 3),这里将排列看成一个首尾相接的圆形排列。

本次案例对周游路线的编码采用路径表示的方案。

杂交

路径表示的杂交算子主要有以下几个:

  1. 部分映射杂交

  1. 次序杂交

  1. 循环杂交

  1. 基于位置杂交

部分映射杂交

部分映射杂交算子通过从一个父体中选择一个子序列,并尽可能多地保持另一个父体中城市的次序和位置的方式产生 后代。具体过程如下:

对给定的两个父体p1,p2,部分映射杂交首先随机地在父体上选择两个杂交点,然后交换父体p1,p2在这两点之间的中间段得到两个后代o1,o2,这一交换同时定义了一个部分映射。这时,o1,o2上除了这两点之间的城市外,其他的城市尚未确定。对o1,o2中尚未确定的城市,分别保留各自父体 中与中间段无冲突的城市,对与中间段有冲突的城市,则执行部分映射,直到与中间段无冲突为止,再将该城市填入相应的位置即可。

例如,给定下列两个父体,随机地选择两个杂交点 (以"|"标记),

p1=(2 6 4 | 7 3 5 8 | 9 1),

p2=(4 5 2 | 1 8 7 6 | 9 3),

p1,p2在这两点之间 的中间段交换得

o1=(# # # | 1 8 7 6 | # #),

o2=(# # # | 7 3 5 8 | # #),

其中,#表示待定城市。这一交换所确定的部分映射为

1<−>7,

8<−>3,

7<−>5,

6<−>8.

然后,再从各自父体中填入与中间段无冲突的城市得

o1=(2 # 4 | 1 8 7 6 | 9 #),

o2=(4 # 2 | 7 3 5 8 | 9 #),

o1,o2中与中间段有冲突的#部分,执行部分映射,直到无冲突为止。例如,o1中的第一个#初始为6,但6与中间段中的城市冲突,部分映射将6映射到8,但8仍与中间段冲突,部分映射到3,3与中间段不冲突,这时第一个#填入3.类似地可以确定其他的#部分,最后所得到的o1,o2如下:

o1=(2 3 4 | 1 8 7 6 | 9 5),

o2=(4 1 2 | 7 3 5 8 | 9 6),

部分映射杂交实现起来比较麻烦,跳来跳去的,所以本次案例不适用部分映射杂交。

次序杂交

次序杂交算子通过从一个父体中选择一个子序列,并保持另一个父体中城市的相对次序的方式产生后代。具体操作如下:

对给定的两个父体p1,p2,次序杂交首先随机地再父体上选择两个杂交点,分别保留父体p1,p2在这两个杂交点之间的中间段得到两个后代o1,o2.这时o1,o2上除了这两个中间段中的城市外,其他的城市尚未确定。对o1中尚未确定的城市,首先将p2中的城市从第二个杂交点开始按其相对次序列出,然后将该序列中在o1中间段中出现的城市删除,再将剩余子序列从第二个杂交点填入o1的相应位置即可。同样的方式可得o2.

例如,给定下列两个父体,随机地选择两个杂交点,

p1=(2 6 4 | 7 3 5 8| 9 1),

p2=(4 5 2 | 1 8 7 6 | 9 3),

保留p1,p2上两个杂交点之间的中间段得

o1=(# # # | 7 3 5 8 | # #),

o2=(# # # | 1 8 7 6 | # #),

其中,#表示待定城市。然后从第二个杂交点 开始,将p2中的城市按原次序列出得

9−3−4−5−2−1−8−7−6,

从中删除o1中间段中的城市得

9−4−2−1−6.

将该子序列从第二个杂交点开始依次填入到o1中得

o1=(2 1 6 | 7 3 5 8 | 9 4),

类似可得

o2=(4 3 5 | 1 8 7 6 | 9 2).

本次案例使用的杂交算子就是次序杂交。

循环杂交

循环杂交算子如下产生后代:后代中的每个城市是某个父体相应位置的城市。具体过程如下:

对给定的两个父体p1=(u1,u2,...,un)p2=(v1,v2,...,vn),先确定后代o1中来自于父体p1中的城市。假定p1中的第一个城市u1o1中,即将o1中的第一个位置填入u1,那么o1中的城市v1不可能来自p2,必来自于p1.假设v1=u1,若ui还没填入o1中,则在o1的第i个位置上填入ui.同样,o1中的城市vi不可能来自p2,必来自于p1.假设vi=ui,若uj还没有填入o1中,则在o1的第j个位置上填入uj.这样继续下去直到将某个uk填入到o1后,p2中与其相应的城市vk也已经填入到o1为止,这就构成一个循环。再将p2中尚未在o1中出现的城市直接填入到o1中相应的位置即可。类似可得后代o2.

例如,给定下列两个父体:

p1=(2 6 4 7 3 5 8 9 1),

p2=(4 5 2 1 8 7 6 9 3),

先从p1中取第一个城市2作为o1的第一个城市得

o1=(2 # # # # # # # #).

p1中城市2对应p2中的城市4,而4还没有填入到o1中,将4填入到o1中得

o1=(2 # 4 # # # # # #).

p1中城市4对应p2中的城市2,但2已经填入o1中了,这完成了一个循环。将p2中尚未在o1中出现的城市直接填入到o1中相应的位置后得

o1=(2 5 4 1 8 7 6 9 3).

类似可得

o2=(4 6 2 7 3 5 8 9 1).

基于位置杂交

基于位置杂交类似于次序杂交,唯一的区别在于:在基于位置杂交中,不是选取父体的一个连续的子序列,而是随机地选取一些位置,再按次序杂交的方式产生后代。

例如,给定下列两个父体:

p1=(2 6 4 7 3 5 8 9 1),

p2=(4 5 2 1 8 7 6 9 3),

假定随机地选取的位置为2,3,6,8.保留p1,p2上这些位置的城市得

o1=(# 6 4 # # 5 # 9 #),

o2=(# 5 2 # # 7 # 9 #),

然后从最后一个选取位置的后面开始,将p2中的城市按原次序列出得

3−4−5−2−1−8−7−6−9,

从中删除o1中已经选取的城市得

3−2−1−8−7.

将该子序列从最后一个选取位置的后面开始依次填入o1中得

o1=(2 6 4 1 8 5 7 9 3),

o2=(6 5 2 4 3 7 8 9 1).

变异

路径表示的变异算子有下面几个:

  1. 倒位变异

  1. 插入变异

  1. 移位变异

  1. 互换变异

倒位变异

倒位变异的过程如下:

首先在父体上随机地选择两个城市,然后将这两个城市之间的子序列反转。

例如,设有父体

(1 2 3 4 5 6 7 8 9).

假定随机地选择的两个城市为3, 7,则对该个体进行倒位变异后得

(1 2 7 6 5 4 3 8 9).

插入变异

插入变异的过程如下:

首先在父体中随机地选择一个城市,然后在该城市插入到某个随机的位置上。

例如,设有父体

(1 2 3 4 5 6 7 8 9).

假定随机地选择的城市为2,随机地选择位置为6,那么将城市2插入到第6个位置上得

(1 3 4 5 6 2 7 8 9).

要注意的是,当将所选择的城市插入到所选择的位置上时,有两种城市移动方式。若所选择的城市在要插入的位置的左边,则所选择城市到插入位置之间的城市(包括插入位置上的城市)向左移动一个位置,否则向右移动一个位置。

移位变异

移位变异的过程如下:

首先在父体中随机地选择一个连续子序列,然后将该子序列插入到某个随机的位置上。

例如,设有父体

(1 2 3 4 5 6 7 8 9).

假定随机地选择的子序列为4 5 6,随机地选择的位置是8,那么经移位变异后得

(1 2 3 7 8 4 5 6 9).

互换变异

互换变异随机地选择两个城市,并将这两个城市互换。

例如,设有父体

(1 2 3 4 5 6 7 8 9).

随机地选择的两个城市为3,7,那么经互换变异后得

(1 2 7 4 5 6 3 8 9).

本次案例采用的变异算子是互换变异。

算法实现

本次使用Matlab来实现遗传算法解决旅行商问题。其中,所涉及到的各子函数放到附录中。

程序目录结构如下:

主函数

  • main.m

%% 遗传算法解决旅行商问题%% 清屏
clear; close; clc; 
%% 加载数据
city = xlsread('./resources/city.xlsx');    % 加载Excel表格数据%% 参数初始化% 主要的参数
popsize = 200;                              % 种群大小
pc = 0.9;                                   % 交叉概率
pm = 0.05;                                  % 变异概率
gen = 1000;                                 % 迭代次数% 次要的参数            
D = Distance(city);                         % 城市距离矩阵
[citycount, ~] = size(city);                % 记录城市个数
best_fitvalue = zeros(gen, 1);              % 记录每一代最优的适应值
minlen = zeros(gen, 1);                     % 记录每一代路径最小值%% 初始化种群
pop = initpop(popsize, citycount);
%% 开始迭代for it = 1:gen
    fitvalue = fitness(pop, D);                                         % 计算适应值
    [best_fitvalue(it), best_index] = max(fitvalue);                    % 记录适应值最高的值与下标
    best_solution = pop(best_index, :);                                 % 记录当前代的最优个体
    minlen(it) = decode(best_solution, D, citycount);                   % 记录每一代的最短路径
    newpop = parent_selection(pop, fitvalue);                           % 父体选择
    newpop = crossover(newpop, pc);                                     % 交叉
    newpop = mutation(newpop, pm);                                      % 变异
    pop = newpop;                                                       % 更新种群
    pop(mod(ceil(rand * citycount), citycount)+1,  :) = best_solution;  % 保留最优个体end%% 画图figure(1)
fori = 2:citycount
    plot([city(best_solution(i-1), 1), city(best_solution(i), 1)], ...
         [city(best_solution(i-1), 2), city(best_solution(i), 2)], 'bo-');
    hold on;
endplot([city(best_solution(citycount), 1), city(best_solution(1), 1)], ...
     [city(best_solution(citycount), 2), city(best_solution(1), 2)], 'ro-');
title(['优化最短距离:', num2str(min(minlen))]);

figure(2)
plot(best_fitvalue);
xlabel('迭代次数')
ylabel('目标函数值')
title('适应值变化曲线')

figure(3)
plot(minlen);
xlabel('迭代次数')
ylabel('目标函数值')
title('最短路径变化曲线')

%% 打印周游路线disp('周游路线:')
words = num2str(best_solution(1));
fori = 2:citycount
   words = append(words, '->', num2str(best_solution(i)));
end
words = append(words, '->', num2str(best_solution(1)));
disp(words);

效果图:

从图1中可以看到优化最短路径为0.11798(单位自己换算,一开始用的点是经纬度表示的)周游路线已绘出,没有交叉的小圈,基本上是最优解了;图2是适应值变化曲线,这个是记录每一代的最优个体的适应值,一直在波动,很符合遗传算法的随机性;图3记录的是每一代的最短路径,基本在第800代左右收敛到最优解;命令窗口也将周游路线打印出来,得本次运行的最短路线为

6->7->3->2->5->9->11->13->15->17->19->20->18->16->14->12->10->8->1->4->6。

附录

各个子函数的Matlab代码实现。

  • Distance.m

  • decode.m

  • initpop.m

  • fitness.m

  • parent_selection.m

  • crossover.m

  • mutation.m

城市数据

>>> city

city =

  109.417724.3085109.444324.3086109.439424.3089109.422624.3090109.447124.3092109.427724.3093109.434724.3101109.416824.3102109.447024.3167109.417524.3186109.446924.3230109.419724.3240109.446724.3285109.421824.3313109.446524.3350109.420524.3372109.446224.3392109.420924.3397109.441324.3415109.435624.3418

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

oom killer理解和日志分析:知识储备

参考&#xff1a;oom killer 详解 oom killer日志分析&#xff0c;这是前篇&#xff0c;准备一些基础知识 带着问题看&#xff1a; 1.什么是oom killer 是Linux内核设计的一种机制&#xff0c;在内存不足的时候&#xff0c;选择一个占用内存较大的进程并kill掉这个进程&…

【JVM系列】JVM内存结构

JVM内存结构 运行时数据区 JAVA运行时内存划分堆&#xff0c;方法区&#xff0c;虚拟机栈&#xff0c;本地方法栈和程序计数器。 线程私有的有&#xff1a; - 程序计数器 - 虚拟机栈 - 本地方法栈​ 线程共享的有&#xff1a; - 堆 - 方法区程序计数器 用来记录当前线程执…

Redundant Paths(双向图的缩点(边联通分量缩成点))

F-Redundant Paths_2022图论班第二章连通性例题与习题 (nowcoder.com) 为了从F(1 \leq F \leq 5000)F(1≤F≤5000)一块牧场(编号为1..F)到另一块牧场&#xff0c;贝西和牛群的其他成员不得不穿过烂苹果树附近。奶牛现在厌倦了经常被迫走一条特定的路&#xff0c;想要建造一些新…

YOLO-V5 系列算法和代码解析(六)—— 分布式训练

文章目录预备知识DPDDPDP和DDP对比YOLO-V5 实际使用参考链接预备知识 为了更好的理解分布式相关的内容&#xff0c;需要提前熟悉一些基本概念和特定的名称。分布式训练涉及到设备端&#xff08;CPU&#xff0c;GPU&#xff09;&#xff0c;算法端&#xff08;梯度更新&#xff…

项目团队承诺管理的3个重要因素

每一个项目都需要一个强有力的领导者&#xff0c;以获得适当的成功机会。与一个优柔寡断、缺乏经验的项目领导者相比&#xff0c;一个有组织的领导者在管理一个精心策划的项目时&#xff0c;更有可能取得项目的成功和客户的满意。再加上一个非常投入和负责任的项目团队&#xf…

[ docker相关知识 ] 删除 docker 拉取的镜像 -- 释放内存

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Speckle 3d数据引擎Python开发实战

在这个教程中&#xff0c;我们将使用 Speckle 数据并使用它来创建一个超级简单的仪表板。 我们将从Speckle流中接收几何图形&#xff0c;更新数据&#xff0c;并使用它来使用 Plotly 和 Dash 进行一些计算和简单绘图。 我们假设你具有 Python 和 Speckle 的一般知识。 如果有任…

信号处理——MATLAB音频信号加噪、滤波

音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结很抱歉大家&#xff0c;最近经常有朋友私信问我关于这篇信号处理的一些问题&#xff0c;因为最近比较忙所以没能一一回复&#xff0c;给大家说句抱歉&#xff0c;希望那些给我私信的人可以看到。大家问的…

golang 垃圾回收-三色标记法(白话版)

对于golang 垃圾回收的了解&#xff0c;我理解更多的就是了解&#xff0c;实际做项目能用到垃圾回收的知识点不多&#xff0c;但有些晦涩难懂的语言&#xff0c;是我们的绊脚石&#xff0c;对于技术怎么能理解就怎么记忆。 1. golang垃圾回收的基础&#xff1a;标记&#xff08…

ESNI 和ECH的前世今生

这边文章中提到过虽然 TLS 能够加密整个通信过程&#xff0c;但是在协商的过程中依旧有很多隐私敏感的参数不得不以明文方式传输&#xff0c;其中最为重要且棘手的就是将要访问的域名&#xff0c;即 SNI&#xff08;Server Name Indication&#xff09;。同时还有用于告知客户端…

javaEE高阶---MyBatis

一 : 什么是MyBatis MyBatis是更简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库的工具.MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的动作 . MyBatis …

[oeasy]python0037_终端_terminal_电传打字机_tty_shell_控制台_console_发展历史

换行回车 回忆上次内容 换行 和 回车 是两回事 换行 对应字节0x0ALine-Feed 水平 不动垂直 向上喂纸 所以是 feed 回车 对应字节0x0DCarriage-Return 垂直 不动水平 回到纸张左侧 可移动的打印头 运输字符 的 装置 (Carriage)回到 行首 所以是 Return tty、terminal、shell、…

【视觉SLAM】DM-VIO: Delayed Marginalization Visual-Inertial Odometry

L. v. Stumberg and D. Cremers, “DM-VIO: Delayed Marginalization Visual-Inertial Odometry,” in IEEE Robotics and Automation Letters, vol. 7, no. 2, pp. 1408-1415, April 2022, doi: 10.1109/LRA.2021.3140129. 论文阅读方法&#xff1a;Title&#xff0c;Abstract…

百趣代谢组学文献分享:学科交叉研究,微生物回收重金属机制研究

发表期刊&#xff1a;Environment International 影响因子&#xff1a;7.297 发表时间&#xff1a;2019年 合作单位&#xff1a;福建农林大学 百趣代谢组学文献分享&#xff0c;该文章是BIOTREE协助客户2019年发表在Environment International上的关于微生物回收重金属机制研…

Tomcat的Connector启动过程分析

一. 前言 前面分析了tomcat的整体架构和tomcat的启动过程&#xff0c;在分析启动过程的时候只讲了整体的启动过程&#xff0c;本篇来重点分析一下tomcat的Connector(连接器)组件的启动过程。 二.从Connector的构造开始 那么org.apache.catalina.connector.Connector是在什么…

文献学习06_利用句法指示符和句子上下文加强关系抽取

论文信息 Subjects: Computation and Language (cs.CL) &#xff08;1&#xff09;题目&#xff1a;Enhancing Relation Extraction Using Syntactic Indicators and Sentential Contexts &#xff08;利用句法指示符和句子上下文加强关系抽取&#xff09; &#xff08;2&…

论文精读:RPM-Net: Robust Point Matching using Learned Features

论文地址:https://arxiv.org/pdf/2003.13479.pdf 点云配准任务 点云配准可以当做一个基础的上游任务,根据从不同视角下获取的点云数据配准为完整的点云数据,下游任务众多 基本任务:求一个变换矩阵,使得两个具有未知点的点云数据重合。 刚性与非刚性: 刚性配准:旋转和平…

Leetcode 121买卖股票的最佳时机

题目描述&#xff1a; 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔…

solr集群配置(使用solr自带的Jetty实现集群配置)

看了很多的资料发现基本集群搭建都是通过tomcat的方式实现的&#xff0c;但是在高版本的solr中&#xff0c;可以通过solr自带的jetty实现集群的搭建 准备 1.虚拟机安装linux 2.安装jdk 3.下载solr并解压 步骤 1.进入到解压后solr的bin目录下&#xff0c;并执行 ./solr -e clo…

赛狐ERP | 如何高效管理亚马逊广告!用这款亚马逊ERP就够了!

亚马逊的广告管理是是每一位亚马逊运营的必修课&#xff0c;除自然流量外&#xff0c;广告来带的流量与转化占比都极高&#xff0c;广告做活了&#xff0c;就是打虎上山&#xff1b;广告搞砸了&#xff0c;就是骑虎难下&#xff1a;不开广告吧没有流量卖不动、开了广告吧财务账…