旅行商问题(TSP). 一个商人欲从自己所在的城市出发,到若干个城市推销商品,然后回到其所在的城市。如何选择一条周游路线,使得商人经过每个城市一次且仅一次后回到起点,并使他所走过的路径最短?
TSP 即Travelling Salesman Problem. 中文翻译过来就是旅行商问题。
旅行商问题是一个典型的NP难问题。NP指的是Non-deterministic Polynomial,即多项式复杂程度的非确定性问题。由于该问题的组合特性,旅行商问题已成为测试新算法的标准问题,如模拟退火、神经网络和演化算法等都用旅行商问题作为测试用例。旅行商问题的一个实例可由一个距离矩阵所给定。
用遗传算法求解旅行商问题时,适应函数可以取为目标函数或目标函数的一个简单变换,选择策略可以是轮盘赌选择,所以算法设计的重点主要集中在以下三个方面:
下面讨论周游路线常用的几种表示及其相应的遗传算子。
主要有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. 1−2−4−3−8−5−9−6−7−1.
显然,每一条周游路线都对应一个近邻表示,但任一近邻排列却不一定对应于一条周游路线。
例如,排列
( 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 1−2−4−1,因而无法对应一条周游路线。所以周游路线的近邻表示就没有必要再往下介绍。
次序表示将一条周游路线表示为 n n n个城市的有序表,其中,表中的第 i i i个元素在 1 1 1到 n − i + 1 n-i+1 n−i+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 n−1个城市的参照排列 ( q 1 , q 2 , . . . , q k ) , k = n − 1 (q_1,q_2,...,q_k),k=n-1 (q1,q2,...,qk),k=n−1,再记录城市 Π 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=n−1中的次序 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=n−1中删除得 n − 2 n-2 n−2个城市的参照排列 ( r 1 , r 2 , . . . , r k ) , k = n − 2 (r_1,r_2,...,r_k), k=n-2 (r1,r2,...,rk),k=n−2,以此类推,直到城市 Π 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 1≤ij≤n−j+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 1−2−4−3−8−5−9−6−7
的次序表示为
( 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 5−1−7−8−9−4−6−2−3−5
可表示为排列 ( 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),这里将排列看成一个首尾相接的圆形排列。
本次案例对周游路线的编码采用路径表示的方案。
路径表示的杂交算子主要有以下几个:
部分映射杂交算子通过从一个父体中选择一个子序列,并尽可能多地保持另一个父体中城市的次序和位置的方式产生 后代。具体过程如下:
对给定的两个父体 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, 9−3−4−5−2−1−8−7−6,
从中删除 o 1 o_1 o1中间段中的城市得
9 − 4 − 2 − 1 − 6. 9-4-2-1-6. 9−4−2−1−6.
将该子序列从第二个杂交点开始依次填入到 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, 3−4−5−2−1−8−7−6−9,
从中删除 o 1 o_1 o1中已经选取的城市得
3 − 2 − 1 − 8 − 7. 3-2-1-8-7. 3−2−1−8−7.
将该子序列从最后一个选取位置的后面开始依次填入 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 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
来实现遗传算法解决旅行商问题。其中,所涉及到的各子函数放到附录中。
程序目录结构如下:
%% 遗传算法解决旅行商问题
%% 清屏
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。
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
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
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
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
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
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
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)