上一次我们使用遗传算法求解了一个较为复杂的多元非线性函数的极值问题,也基本了解了遗传算法的实现基本步骤。这一次,我再以经典的TSP问题为例,更加深入地说明遗传算法中选择、交叉、变异等核心步骤的实现。而且这一次解决的是离散型问题,上一次解决的是连续型问题,刚好形成对照。
首先介绍一下TSP问题。TSP(traveling salesman problem,旅行商问题)是典型的NP完全问题,即其最坏情况下的时间复杂度随着问题规模的增大按指数方式增长,到目前为止还没有找到一个多项式时间的有效算法。TSP问题可以描述为:已知n个城市之间的相互距离,某一旅行商从某一个城市出发,访问每个城市一次且仅一次,最后回到出发的城市,如何安排才能使其所走的路线最短。换言之,就是寻找一条遍历n个城市的路径,或者说搜索自然子集X={1,2,...,n}(X的元素表示对n个城市的编号)的一个排列P(X)={V1,V2,....,Vn},使得Td=∑d(Vi,Vi+1)+d(Vn,V1)取最小值,其中,d(Vi,Vi+1)表示城市Vi到Vi+1的距离。TSP问题不仅仅是旅行商问题,其他许多NP完全问题也可以归结为TSP问题,如邮路问题,装配线上的螺母问题和产品的生产安排问题等等,也使得TSP问题的求解具有更加广泛的实际意义。
再来说针对TSP问题使用遗传算法的步骤。
(1)编码问题:由于这是一个离散型的问题,我们采用整数编码的方式,用1~n来表示n个城市,1~n的任意一个排列就构成了问题的一个解。可以知道,对于n个城市的TSP问题,一共有n!种不同的路线。
(2)种群初始化:对于N个个体的种群,随机给出N个问题的解(相当于是染色体)作为初始种群。这里具体采用的方法是:1,2,...,n作为第一个个体,然后2,3,..n分别与1交换位置得到n-1个解,从2开始,3,4,...,n分别与2交换位置得到n-2个解,依次类推。(如果这样还不够初始种群的数量,可以再考虑n,n-1,...,1这个序列,然后再按照相同的方法生成,等等)
(3)适应度函数:设一个解遍历初始行走的总距离为D,则适应度fitness=1/D.即总距离越高,适应度越低,总距离越低(解越好),适应度越高。
(4) 选择操作:个体被选中的概率与适应度成正比,适应度越高,个体被选中的概率越大。这里仍然采用轮盘赌法。
(5) 交叉操作:交叉操作是遗传算法最重要的操作,是产生新个体的主要来源,直接关系到算法的全局寻优能力,这里采用部分映射交叉。比如对于n=10的情况,对于两个路径: 1 2 4 5 6 3 9 10 8 7
3 9 7 6 8 10 5 1 2 4
随机产生两个[1,10]之间的随机数r1,r2,代表选择交叉的位置,比如r1=2,r2=4,如上图标红的位置,将第一个个体r1到r2之间的基因(即城市序号)与第二个个体r1到r2之间的基因交换,交换之后变为:
1 9 7 6 6 3 9 10 8 7
3 2 4 5 8 10 5 1 2 4
黄色部分表示交叉过来的基因,这个时候会发现可能交叉过来的基因与原来其他位置上的基因有重复,容易直到,第一个个体重复基因的数目与第二个个体重复基因的数目是相同的(这里都是3个),只需要把第一个个体重复的基因(用绿色标识)与第二个个体重复的基因做交换,即可以消除冲突。消除冲突之后的解如下:
1 9 7 6 5 3 2 10 8 4
3 2 4 5 8 10 6 1 9 7
(6)变异操作:变异操作采取对于一个染色体(即个体)随机选取两个基因进行交换的策略。比如对于染色体:
2 4 6 10 3 1 9 7 8 5
随机选取了两个位置p1=3,p2=8(标红位置),交换这两个位置的基因,得到新的染色体为:
2 4 7 10 3 1 9 6 8 5
(7) 进化逆转操作:这个是标准的遗传算法没有的,是我们为了加速进化而加入的一个操作。这里的进化是指逆转操作具有单向性,即只有逆转之后个体变得更优才会执行逆转操作,否则逆转无效。具体的方法是,随机产生[1,10](这里仍然以10个城市为例)之间的两个随机数r1和r2(其实也是允许相同的,只是r1,r2相同之后,逆转自然无效,设置交叉变异都是无效的,但是这不会经常发生),然后将r1和r2之间的基因进行反向排序。比如对于染色体:
1 3 4 2 10 9 8 7 6 5
r1=3,r2=5,它们之间的基因(标红位置)反向排列之后得到的染色体如下:
1 3 10 2 4 9 8 7 6 5
根据以上的步骤,我们就可以比较容易写出用遗传算法求解TSP问题的具体代码了,这里仍然使用C语言。先以规模比较小的城市为例,这里取14个,城市之间的距离会直接在代码中给出。代码如下:
/* *遗传算法(GA) 解决TSP 问题 *案例参考自《MATLAB 智能算法30个案例分析》 *本例以14个城市为例,14个城市的位置坐标如下(括号内第一个元素为X坐标,第二个为纵坐标):1:(16.47,96.10) 2:(16.47,94.44) 3:(20.09,92.54) *4:(22.39,93.37) 5:(25.23,97.24) 6:(22.00,96.05) 7:(20.47,97.02) 8:(17.20,96.29) 9:(16.30,97.38) 10:(14.05,98.12) 11:(16.53,97.38) *12:(21.52,95.59) 13:(19.41,97.13) 14:(20.09,92.55) *遗传算法实现的步骤为:(1)编码 (2) 种群初始化 (3) 构造适应度函数 (4) 选择操作 (5) 交叉操作 (6) 变异操作 (7) 进化逆转操作 * 具体实现的步骤这里不详细说,参考《MATLAB 智能算法30个案例分析》P38 - P40 * update in 16/12/4 * author:Lyrichu * email:919987476@qq.com */ #include<stdio.h> #include<stdlib.h> #include<math.h> #include<time.h> #define maxgen 200 // 最大进化代数 #define sizepop 100 // 种群数目 #define pcross 0.6 // 交叉概率 #define pmutation 0.1 // 变异概率 #define lenchrom 14 // 染色体长度(这里即为城市个数) double city_pos[lenchrom][2] = { { 16.47,96.10},{ 16.47,94.44},{ 20.09,92.54},{ 22.39,93.37},{ 25.23,97.24},{ 22.00,96.05},{ 20.47,97.02}, { 17.20,96.29},{