遗传算法解决旅行商问题(TSP)

news2024/9/21 12:21:17

遗传算法解决旅行商问题

  • 作者:Cukor丘克
  • 环境:MatlabR202a + vscode

问题描述

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

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

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

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

  1. 采用适当的方法对周游路线编码
  2. 设计专门的遗传算子,以避免不可行性
  3. 防止过早收敛

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

周游路线编码

主要有3种编码表示

  1. 近邻表示
  2. 次序表示
  3. 路径表示

近邻表示

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

例如,排列

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

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

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

例如,排列

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

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

次序表示

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

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

对给定的一条路径 Π 1 − Π 2 − . . . − Π n Π_1-Π_2-...-Π_n Π1Π2...Πn,首先记录城市 Π 1 Π_1 Π1在参照排列 ( p 1 , p 2 , . . . , p n ) (p_1,p_2,...,p_n) (p1,p2,...,pn)中的次序 i 1 i_1 i1,将 Π 1 Π_1 Π1 ( p 1 , p 2 , . . . , p n ) (p_1,p_2,...,p_n) (p1,p2,...,pn)中删除得 n − 1 n-1 n1个城市的参照排列 ( q 1 , q 2 , . . . , q k ) , k = n − 1 (q_1,q_2,...,q_k),k=n-1 (q1,q2,...,qk),k=n1,再记录城市 Π 2 Π_2 Π2在参照排列 ( q 1 , q 2 , . . . , q k ) , k = n − 1 (q_1,q_2,...,q_k),k=n-1 (q1,q2,...,qk),k=n1中的次序 i 2 i_2 i2,然后将城市 Π 2 Π_2 Π2 ( q 1 , q 2 , . . . , q k ) , k = n − 1 (q_1,q_2,...,q_k),k=n-1 (q1,q2,...,qk),k=n1中删除得 n − 2 n-2 n2个城市的参照排列 ( r 1 , r 2 , . . . , r k ) , k = n − 2 (r_1,r_2,...,r_k), k=n-2 (r1,r2,...,rk),k=n2,以此类推,直到城市 Π n Π_n Πn的次序 i n i_n in记录下来为止。有序表 ( i 1 , i 2 , . . . , i n ) (i_1,i_2,...,i_n) (i1,i2,...,in)称为路径 Π 1 − Π 2 − . . . − Π n Π_1-Π_2-...-Π_n Π1Π2...Πn的次序表示。注意 i j i_j ij的取值范围为 1 ≤ i j ≤ n − j + 1 1 \le i_j \le n-j+1 1ijnj+1.

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

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

的次序表示为

( 1   1   2   1   4   1   3   1   1 ) (1~1~2~1~4~1~3~1~1) (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-5 5178946235

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

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

杂交

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

  1. 部分映射杂交
  2. 次序杂交
  3. 循环杂交
  4. 基于位置杂交

部分映射杂交

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

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

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

p 1 = ( 2   6   4   ∣   7   3   5   8 ∣   9   1 ) , p_1=(2~6~4~|~7~3~5~8|~9~1), p1=(2 6 4  7 3 5 8∣ 9 1),

p 2 = ( 4   5   2   ∣   1   8   7   6   ∣   9   3 ) , p_2=(4~5~2~|~1~8~7~6~|~9~3), p2=(4 5 2  1 8 7 6  9 3),

p 1 , p 2 p_1,p_2 p1,p2在这两点之间 的中间段交换得

o 1 = ( #   #   #   ∣   1   8   7   6   ∣   #   # ) , o_1=(\#~\#~\#~|~1~8~7~6~|~\#~\#), o1=(# # #  1 8 7 6  # #),

o 2 = ( #   #   #   ∣   7   3   5   8   ∣   #   # ) , o_2=(\#~\#~\#~|~7~3~5~8~|~\#~\#), o2=(# # #  7 3 5 8  # #),

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

1 < − > 7 , 1<->7, 1<>7,

8 < − > 3 , 8<->3, 8<>3,

7 < − > 5 , 7<->5, 7<>5,

6 < − > 8. 6<->8. 6<>8.

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

o 1 = ( 2   #   4   ∣   1   8   7   6   ∣   9   # ) , o_1=(2~\#~4~|~1~8~7~6~|~9~\#), o1=(2 # 4  1 8 7 6  9 #),

o 2 = ( 4   #   2   ∣   7   3   5   8   ∣   9   # ) , o_2=(4~\#~2~|~7~3~5~8~|~9~\#), o2=(4 # 2  7 3 5 8  9 #),

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

o 1 = ( 2   3   4   ∣   1   8   7   6   ∣   9   5 ) , o_1=(2~3~4~|~1~8~7~6~|~9~5), o1=(2 3 4  1 8 7 6  9 5),

o 2 = ( 4   1   2   ∣   7   3   5   8   ∣   9   6 ) , o_2=(4~1~2~|~7~3~5~8~|~9~6), o2=(4 1 2  7 3 5 8  9 6),

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

次序杂交

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

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

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

p 1 = ( 2   6   4   ∣   7   3   5   8   ∣   9   1 ) , p_1=(2~6~4~|~7~3~5~8~|~9~1), p1=(2 6 4  7 3 5 8  9 1),

p 2 = ( 4   5   2   ∣   1   8   7   6   ∣   9   3 ) , p_2=(4~5~2~|~1~8~7~6~|~9~3), p2=(4 5 2  1 8 7 6  9 3),

保留 p 1 , p 2 p_1,p_2 p1,p2上两个杂交点之间的中间段得

o 1 = ( #   #   #   ∣   7   3   5   8   ∣   #   # ) , o_1=(\#~\#~\#~|~7~3~5~8~|~\#~\#), o1=(# # #  7 3 5 8  # #),

o 2 = ( #   #   #   ∣   1   8   7   6   ∣   #   # ) , o_2=(\#~\#~\#~|~1~8~7~6~|~\#~\#), o2=(# # #  1 8 7 6  # #),

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

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

从中删除 o 1 o_1 o1中间段中的城市得

9 − 4 − 2 − 1 − 6. 9-4-2-1-6. 94216.

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

o 1 = ( 2   1   6   ∣   7   3   5   8   ∣   9   4 ) , o_1=(2~1~6~|~7~3~5~8~|~9~4), o1=(2 1 6  7 3 5 8  9 4)

类似可得

o 2 = ( 4   3   5   ∣   1   8   7   6   ∣   9   2 ) . o_2=(4~3~5~|~1~8~7~6~|~9~2). o2=(4 3 5  1 8 7 6  9 2).

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

循环杂交

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

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

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

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

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

先从 p 1 p_1 p1中取第一个城市2作为 o 1 o_1 o1的第一个城市得

o 1 = ( 2   #   #   #   #   #   #   #   # ) . o_1=(2~\#~\#~\#~\#~\#~\#~\#~\#). o1=(2 # # # # # # # #).

p 1 p_1 p1中城市2对应 p 2 p_2 p2中的城市4,而4还没有填入到 o 1 o_1 o1中,将4填入到 o 1 o_1 o1中得

o 1 = ( 2   #   4   #   #   #   #   #   # ) . o_1=(2~\#~4~\#~\#~\#~\#~\#~\#). o1=(2 # 4 # # # # # #).

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

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

类似可得

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

基于位置杂交

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

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

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

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

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

o 1 = ( #   6   4   #   #   5   #   9   # ) , o_1=(\#~6~4~\#~\#~5~\#~9~\#), o1=(# 6 4 # # 5 # 9 #),

o 2 = ( #   5   2   #   #   7   #   9   # ) , o_2=(\#~5~2~\#~\#~7~\#~9~\#), o2=(# 5 2 # # 7 # 9 #),

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

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

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

3 − 2 − 1 − 8 − 7. 3-2-1-8-7. 32187.

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

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

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

变异

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

  1. 倒位变异
  2. 插入变异
  3. 移位变异
  4. 互换变异

倒位变异

倒位变异的过程如下:

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

例如,设有父体

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

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

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

插入变异

插入变异的过程如下:

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

例如,设有父体

( 1   2   3   4   5   6   7   8   9 ) . (1~2~3~4~5~6~7~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~3~4~5~6~2~7~8~9). (1 3 4 5 6 2 7 8 9).

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

移位变异

移位变异的过程如下:

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

例如,设有父体

( 1   2   3   4   5   6   7   8   9 ) . (1~2~3~4~5~6~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~7~8~4~5~6~9). (1 2 3 7 8 4 5 6 9).

互换变异

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

例如,设有父体

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

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

( 1   2   7   4   5   6   3   8   9 ) . (1~2~7~4~5~6~3~8~9). (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)
for i = 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;
end
plot([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));
for i = 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
function D = Distance(city)
%Distance - 计算两个城市之间的距离
%
% Syntax: D = Distance(city)
%
% Long 返回的是一个距离矩阵,记录每两个城市之间的距离
    [n, ~] = size(city);
    D = ones(n, n);
    for i = 1:n
        for j = i:n
            D(i, j) = sqrt((city(i, 1) - city(j, 1)).^2 + (city(i, 2) - city(j, 2)).^2);
            D(j, i) = D(i, j);
        end
    end
end
  • decode.m
function path_len = decode(code, D, n)
%decode - 对种群个体解码
%
% Syntax: path_len = decode(code, D, n)
%
% Long 返回的是路径长度,一个1*1的矩阵(变量)
    path_len = 0;
    for j = 2:n
        path_len = path_len + D(code(j-1), code(j));
    end
    path_len = path_len + D(code(n), code(1));
end
  • initpop.m
function pop = initpop(popsize, citycount)
%initpop - 初始化种群
%
% Syntax: pop = initpop(popsize, citycount)
%
% Long 返回一个popsize*citycount的矩阵
    pop = ones(popsize, citycount);
    for i = 1:popsize
        pop(i, :) = randperm(citycount);
    end
end
  • fitness.m
function fitvalue = fitness(pop, D)
%fitness - 计算适应值
%
% Syntax: fitvalue = fitness(pop, D)
%
% Long 返回的是px行1列的矩阵,记录的是当前种群pop的每一个个体的适应值
    [px, py] = size(pop);
    fitvalue = zeros(px, 1);
    for i = 1:px
        fitvalue(i) = decode(pop(i, :), D, py);
    end
    fitvalue = 1./fitvalue;
    fitvalue = fitvalue./sum(fitvalue);  % 适应值归一化
end
  • parent_selection.m
function newpop = parent_selection(pop, fitvalue)
%parent_selection - 父体选择
%
% Syntax: newpop = parent_selection(pop, fitvalue)
%
% Long 返回和pop一样大小的矩阵,新种群,采用轮盘赌选择策略
    [px, py] = size(pop);
    newpop = zeros(px, py);
    %% 设计轮盘
    p = cumsum(fitvalue);   % 在计算适应值函数中已经做好归一化
    %% 转动轮盘
    r = sort(rand(px, 1));
    j = 1;
    for i = 1:px
        while r(i) > p(j)
            j = j + 1;
        end
        % r(i) <= p(j)
        newpop(i, :) = pop(j, :);
    end
end
  • crossover.m
function newpop = crossover(pop, pc)
%crossover - 交叉
%
% Syntax: newpop = crossover(pop, pc)
%
% Long description
    [px, ~] = size(pop);
    newpop = pop;
    for i = 2:2:px
        if rand <= pc
            [newpop(i, :), newpop(i-1, :)] = sub_crossover(pop(i, :), pop(i-1, :));
        end
    end
end

function [newcode1, newcode2] = sub_crossover(code1, code2)
%sub_crossover - 子交叉函数
%
% Syntax: [newcode1, newcode2] = sub_crossover(code1, code2)
%
% Long 采用次序杂交的方式进行杂交
    newcode1 = code1;
    newcode2 = code2;
    n = length(code1);
    front = -1;
    rear = -1;
    while front < 1 || rear <= front || rear >= n
        front = ceil(rand * n);
        rear = ceil(rand * n);
    end
    p2 = code2;
    p1 = code1;
    for i = front:rear
        p2(p2 == code1(i)) = [];
        p1(p1 == code2(i)) = [];
    end
    j = 1;
    for i = 1:n
        if i < front || i > rear
            newcode1(i) = p2(j);
            newcode2(i) = p1(j);
            j = j + 1;
        end
    end
end
  • mutation.m
function newpop = mutation(pop, pm)
%mutation - 变异
%
% Syntax: newpop = mutation(pop, pm)
%
% Long 采用互换变异算子
    [px, py] = size(pop);
    newpop = pop;
    cnt = ceil(rand*py/2);      % 确定变异点的个数
    pos = mod(ceil(rand(px, cnt) * py), py) + 1;  % 变异点的位置
    for i = 1:px
        if rand <= pm
            for j = 2:2:cnt
                temp = newpop(i, pos(i, j));
                newpop(i, pos(i, j)) = newpop(i, pos(i, j-1));
                newpop(i, pos(i, j-1)) = temp;
            end
        end
    end
end

城市数据

>>> city

city =

  109.4177   24.3085
  109.4443   24.3086
  109.4394   24.3089
  109.4226   24.3090
  109.4471   24.3092
  109.4277   24.3093
  109.4347   24.3101
  109.4168   24.3102
  109.4470   24.3167
  109.4175   24.3186
  109.4469   24.3230
  109.4197   24.3240
  109.4467   24.3285
  109.4218   24.3313
  109.4465   24.3350
  109.4205   24.3372
  109.4462   24.3392
  109.4209   24.3397
  109.4413   24.3415
  109.4356   24.3418

下面是我在bilibli上发布的视频链接:
Cukor丘克-遗传算法解决旅行商问题(TSP)

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

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

相关文章

SpringBoot3初体验 - 第457篇

历史文章&#xff08;文章累计450&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 5个月的精华&#xff1a;Spring/SpringB…

【JavaScript】多维数组及数组扁平化

文章目录【JavaScript】多维数组及数组扁平化一. 概念二. 数组扁平化方式一&#xff1a;使用flat()方法方式二&#xff1a;使用递归方式三&#xff1a;使用reduce()方式处理三. 对象数组的联合操作【JavaScript】多维数组及数组扁平化 一. 概念 (1) 一维数组&#xff1a;数组…

深度学习Week13-火灾温度预测(LSTM)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考文章&#xff1a;第R2周&#xff1a;LSTM-火灾温度预测&#xff08;训练营内部可读&#xff09;&#x1f356; 作者&#xff1a;K同学啊任务说明&#xff1a;数据集中提供了火灾温度&…

c++知识点总结

文章目录1.引用2. 重载3. extern “C”4.构造函数5.析构函数6.类和对象7.面向对象模型8.继承9.多态10.函数模板11.类模板12.STL1.引用 不要返回局部变量的引用&#xff0c;调用函数执行后局部变量会销毁 2. 重载 是C多态的特性&#xff08;静态多态&#xff09;。同一个函数名代…

正点原子STM32(基于HAL库)

正点原子B站视频地址&#xff1a;https://www.bilibili.com/video/BV1bv4y1R7dp?p1&vd_sourcecc0e43b449de7e8663ca1f89dd5fea7d 目录单片机简介Cortex-M介绍初识STM32STM32命名规则STM32 选型STM32 设计数据手册最小系统IO 分配STM32启动过程分析启动模式启动文件分析启动…

基于彩色的图像分割

图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出感兴趣目标的技术和过程。它是由图像处理到图像分析的关键步骤。现有的图像分割方法主要分以下几类&#xff1a;基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法以及基于特定理论的分割方法等。从数…

小程序03/ uni-app认识目录结构 、uni-app应用生命周期 和 生命周期钩子、uni-app页面生命周期 和 生命周期钩子

一.uni-app认识目录结构 二.uni-app应用生命周期 和 生命周期钩子 位置: uni-app 在App.vue中监听 在页面监听无效 说明: App.vue是uni-app的主组件 所有页面都是在App.vue 下进行切换的、是页面入口文件 但是App.vue 本身不是页面 这里不能编写视图元素 也就是没有<tmpl…

Git(一) - Git 概述

01_尚硅谷_Git_课程介绍_哔哩哔哩_bilibili Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以高效的处理从小型到大型的各种项目。 一、何为版本控制 版本控制是一种记录文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。 版本控制其实最主要的是可以…

java 瑞吉外卖优化day2 Nginx

Nginx概述 下载与安装 可以到Nginx官方网站下载Nginx的安装包&#xff0c;地址为&#xff1a;https://nginx.org/en/download.html 安装过程&#xff1a; 1、安装依赖包 yum -y install gcc pcre-devel zlib-devel openssl openssl-devel 先yum install wget &#xff0c;…

随机数 - 时间种子的方案与实践

1.应用场景 主要弄清楚设置随机数种子的方法&#xff0c;可用于游戏开发当中的时间种子从而产生合理的随机数&#xff0c;避免出现bug。 2.学习/操作 1.文档阅读 07 | 带你快速上手 Lua-极客时间 2.整理输出 2.1 什么是种子 现在很多朋友下载东西时都会用bt种子文件&#xff0…

5分钟带你学习 linux 收发邮件步骤详解 at命令详解 crontab命令详解 附加at crontab命令练习

linux 收发邮件步骤详解 1.安装软件yum install mailx -yyum install sendmail -y 2.启动服务sendmailsystemctl start sendmail 3.更改配置vim /etc/mail.rc at命令详解 实例&#xff1a; crontab命令详解 实例&#xff1a; linux 收发邮件步骤详解 1.安装软件 yum…

拜占庭将军问题

前言 在分布式系统中交换信息, 部分成员可能出错导致发送了错误的信息, 在这种情况下如何达成共识. 这就是拜占庭将军问题所要解决的. 问题的简要描述如下: 3个军队协同作战(为了简单易懂, 以3个军队描述)每支军队的作战策略有两种"进攻"和"撤退"每个军…

SparkLaunch提交Spark任务到Yarn集群

SparkLaunch提交任务1.提交Spark任务的方式2.SparkLaunch 官方接口3.任务提交流程及实战1.提交Spark任务的方式 通过Spark-submit 提交任务通过Yarn REST Api提交Spark任务通过Spark Client Api 的方式提交任务通过SparkLaunch 自带API提交任务基于Livy的方式提交任务&#xf…

深拷贝浅拷贝的区别?如何实现一个深拷贝

一、数据类型存储 前面文章我们讲到&#xff0c;JavaScript中存在两大数据类型&#xff1a; 基本类型引用类型 基本类型数据保存在在栈内存中 引用类型数据保存在堆内存中&#xff0c;引用数据类型的变量是一个指向堆内存中实际对象的引用&#xff0c;存在栈中 二、浅拷贝…

【2】SCI易中期刊推荐——遥感图像领域(2区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…

2022年最有开创性的10篇AI论文总结

2022年随着聊天GPT和Mid - journey和Dall-E等图像生成器的流行&#xff0c;我们看到了整个人工智能领域的重大进展。在人工智能和计算机科学的时代&#xff0c;这是令人振奋的一年。本文我们总结了在2022年发表的最具开创性的10篇论文&#xff0c;无论如何你都应该看看。 1、Al…

Apache Calcite初识

Calcite原理和代码讲解(一) https://blog.csdn.net/qq_35494772/article/details/118887267quickstart:Apache Calcite精简入门与学习指导 https://blog.51cto.com/xpleaf/2639844quickstart:多源数据的关联 csv和mem数据类型 https://cloud.tencent.com/developer/article/162…

【Javassist】快速入门系列14 使用Javassist导入包路径

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 04 使用Javassist更改整个方法体 05 当有指定方法调用时替换方法调用的内容 06 当有构造方法调用时替换方法调用的内容 07 当检测到字段被访问时使用语…

CSS复习(一)

CSS复习1.前言2. CSS介绍2.1 CSS的引入方式2.2 选择器2.2 颜色的赋值方式3. 补充4.display4.1 盒子模型4.1.1 盒子模型之宽高盒子模型之外边距盒子模型之边框盒子模型之内边距4.2 文本问题1.前言 首先补充一下部分相关知识&#xff1a; 分区标签自身没有显示效果&#xff0c;…

【算法】kmp、Trie、并查集、堆

文章目录1.kmp2.Trie3.并查集4.堆1.kmp KMP 的精髓就是 next 数组&#xff1a;也就是用 next[j] k;简单理解就是&#xff1a;来保存子串某个位置匹配失败后&#xff0c;回退的位置。 给定一个字符串 S&#xff0c;以及一个模式串 P&#xff0c;所有字符串中只包含大小写英文字…