参考
TSP问题
- 旅行商问题是一个np难问题,此文是笔者最近学了Node.js和刚好需要做一份遗传算法解决TSP问题,就写了这一篇博文。
- TSP问题是组合数学中一个古老而又困难的问题,也是一个典型的组合优化问题,现已归入NP完备问题类。NP问题用穷举法不能在有效时间内求解,所以只能使用启发式搜索。而遗传算法是求解此类问题比较实用、有效的方法之一。
问题描述
- 假设有n个可直达的城市,一销售商从其中的某一城市出发,不重复地走完其余n-1个城市并回到原出发点,在所有可能的路径中求出路径长度最短的一条(任意两个城市可以相互直达)。
- 给出30个城市的位置信息表
| 城市编号 | 坐标 |城市编号|坐标|城市编号|坐标|
|----------|-------|------|----|------|----|
|1 |(87,7) | 11| (58,69)| 21| (4,50)|
|2 |(91,38)| 12| (54,62)| 22| (13,40)|
|3 |(83,46)| 13| (51,67)| 23| (18,40)|
|4 |(71,44)| 14| (37,84)| 24| (24,42)|
|5 |(64,60)| 15| (41,94)| 25| (25,38)|
|6 |(68,58)| 16| (2,99) | 26| (41,26)|
|7 |(83,69)| 17| (7,64) | 27| (45,21)|
|8 |(87,76)| 18| (22,60)| 28| (44,35)|
|9 |(74,78)| 19| (25,62)| 29| (58,35)|
|10 |(71,71)| 20|(18,54) | 30| (62,32)|
- 距离:两城市的直线距离
- 如:计算城市c1和c2的距离 - dx = |x1 - x2|, dy = |y1 - y2|, d = Math.sqrt(dxdx + dydy)
算法流程
- 轮盘赌法选择后代
|种群中的个体 |1|2|3|4|5|6|
|------------|-|-|-|--|--|---|
|适应值 |8 |2|17|7|4|11|
|轮盘赌法范围 |0
7 |89 |1026 | 2733 |3437 | 3848 |
|产生的随机数| 23属于10~26的范围
|选中的后代 | 因此选中3个体
- 染色体交叉
- 现在Parent1选取连续的一段基因A(数组元素),然后再从Parent2中选取不重复于A中基因组合B,然后将A、B拼接起来
- Parent1 -> 3 7 8 6 1 9 2 4 5
- Parent2 -> 8 2 6 9 4 5 3 1 7
- child -> 8 6 1 9 2 4 5 3 7
- A + B -> 8 6 1 9 2 4 5 3 7
- 变异(针对种群中除去最优的个体,使其有几率发生往好的方向变异)
- 为了脱离染色体交叉可能陷入的局部最优
- 选取两个基因的下标,记为gen1,gen2,m = Math.floor(gen1 + gen2)/ 2
- 将a[gen1...m]与a[m+1...gen2]依次交换位置
let gen1 = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
let gen2 = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
if(gen1 > gen2) {
let temp = gen1
gen1 = gen2
gen2 = temp
}
for(let m = gen1; m < Math.floor((gen1 + gen2) / 2); m ++) {
let temp = t_colony[m]
t_colony[m] = t_colony[gen1 + gen2 - m]
t_colony[gen1 + gen2 - m] = temp
}
- 计算适应值
- 采用N-obj:N是一个极大数,obj是个体的总路径之和,适应值越大,说明此个体越适应
- 具体算法代码细节,可以看原代码,我每行注释的很清晰
- 但要注意的是求解30个城市和10个城市的问题切换需要修改的地方
- Tsp.js

- module/init/const.js

- module/Cross.js

算法目录解析
- Tsp.js:算法的入口文件,负责导入各种功能模块
- module文件夹
- data.json:存储30个城市的数据
- data1.json:存储前10个城市的数据
- Select.js:从种群中通过轮盘赌法选择父代和母代
- Mutation.js:子代的染色体发生变异
- Copy:将一个数组的元素赋值到另一个数组中
- Cross.js:父母染色体按某种方式进行交叉(数组元素按位置进行交换)
- GetFittness.js:获取某个个体的适应值
- init文件夹中的文件负责种群的初始化工作
- const.js(存储相关的系统常量)

- CityDistance.js:计算每个城市间的距离
- InitColony.js:初始化种群
- Check.js:检查新生成的个体是否已经存在当前种群中
- CalFitness:计算种群中每个个体的适应值

最优解和实验结果分析
30个城市
- 最优解:1 2 3 4 6 5 7 8 9 10 11 12 13 14 15 16 17 19 18 20 21 22 23 24 25 28 26 27 29 30
- 实验结果分析
- 其路径长度为:424.869292

10个城市
- 最优解: 0 3 5 4 9 8 7 6 2 1 0
- 实验结果分析
- 路径长度是166.541336

源代码
data.json
[
{
"x":87,
"y":7
},
{
"x":91,
"y":38
},
{
"x":83,
"y":46
},
{
"x":71,
"y":44
},
{
"x":64,
"y":60
},
{
"x":68,
"y":58
},
{
"x":83,
"y":69
},
{
"x":87,
"y":76
},
{
"x":74,
"y":78
},
{
"x":71,
"y":71
},
{
"x":58,
"y":69
},
{
"x":54,
"y":62
},
{
"x":51,
"y":67
},
{
"x":37,
"y":84
},
{
"x":41,
"y":94
},
{
"x":2,
"y":99
},
{
"x":7,
"y":64
},
{
"x":22,
"y":60
},
{
"x":25,
"y":62
},
{
"x":18,
"y":54
},
{
"x":4,
"y":50
},
{
"x":13,
"y":40
},
{
"x":18,
"y":40
},
{
"x":24,
"y":42
},
{
"x":25,
"y":38
},
{
"x":41,
"y":26
},
{
"x":45,
"y":21
},
{
"x":44,
"y":35
},
{
"x":58,
"y":35
},
{
"x":62,
"y":32
}
]
data1.json
[
{
"x":87,
"y":7
},
{
"x":91,
"y":38
},
{
"x":83,
"y":46
},
{
"x":71,
"y":44
},
{
"x":64,
"y":60
},
{
"x":68,
"y":58
},
{
"x":83,
"y":69
},
{
"x":87,
"y":76
},
{
"x":74,
"y":78
},
{
"x":71,
"y":71
}
]
Tsp.js
const MAX_EPOC = require('./module/init/const.js').MAX_EPOC
let COUNT = require('./module/init/const.js').COUNT
const CityDistance = require('./module/init/CityDistance.js')
const InitColony = require('./module/init/InitColony.js')
const CalFitness = require('./module/init/CalFitness.js')
const Select = require('./module/Select.js')
const Cross = require('./module/Cross.js')
const Mutation = require('./module/Mutation.js')
class TSP {
colony = []
fitness = []
Distance = []
BestRooting = []
BestFitness = []
BestPath= null
BestIndex = null
SelectParent = []
}
let city = new TSP()
const fs = require('fs')
fs.readFile('./data1.json',(err,data) => {
if(err) {
return err
}
const tsp_data = JSON.parse(data.toString())
let distance = CityDistance(tsp_data)
InitColony(city,tsp_data)
CalFitness(city,distance)
while(city.BestRooting.join(' ') != '0 3 5 4 9 8 7 6 2 1 0'){
Select(city)
Cross(city,distance)
Mutation(city,distance)
CalFitness(city,distance)
console.log('循环次数:',COUNT ++,"路径长:",city.BestPath,"路径序列:",city.BestRooting.join(' '))
}
console.log("\n求得最优路径序列:",city.BestRooting.join(' '))
console.log("求得最短路径长:",city.BestPath)
})
module文件夹
Select.js
const POP_SIZE = require('./init/const').POP_SIZE
module.exports = function(city) {
city.SelectParent = []
let SelectP = [0]
for(let i = 0; i < POP_SIZE; i ++) {
SelectP[i + 1] = SelectP[i] + city.fitness[i]
}
let r1 = Math.random() * SelectP[POP_SIZE - 1]
let r2 = Math.random() * SelectP[POP_SIZE - 1]
for(let i = 0; i < POP_SIZE - 1; i ++) {
if(r1 >= SelectP[i] && r1 <= SelectP[i + 1]) {
city.SelectParent.push(i)
break
}
}
for(let j = 0; j < POP_SIZE - 1; j ++) {
if(r2 >= SelectP[j] && r2 <= SelectP[j + 1]) {
city.SelectParent.push(j)
break
}
}
}
Mutation.js
let {POP_SIZE,CITY_NUM} = require('./init/const')
const Copy = require('./Copy.js')
const GetFittness = require('./GetFittness.js')
module.exports = function(city,distance) {
for(let i = 0; i < POP_SIZE; i ++) {
let t_colony = []
let flag = 0
let count = 0
do{
if(i != city.BestIndex) {
Copy(t_colony,city.colony[i])
let gen1 = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
let gen2 = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
if(gen1 > gen2) {
let temp = gen1
gen1 = gen2
gen2 = temp
}
for(let m = gen1; m < Math.floor((gen1 + gen2) / 2); m ++) {
let temp = t_colony[m]
t_colony[m] = t_colony[gen1 + gen2 - m]
t_colony[gen1 + gen2 - m] = temp
}
if(GetFittness(t_colony,distance) > GetFittness(city.colony[i],distance)) {
Copy(city.colony[i],t_colony)
flag = 1
}
count ++
}
}while(t_colony.length && flag == 0 && count < 3)
}
}
GetFittness.js
const {CITY_NUM,N} = require("./init/const")
module.exports = function(colony,distance) {
let start,end
let distance_sum = 0
for(let i = 0; i < CITY_NUM; i ++) {
start = colony[i]
end = colony[i + 1]
distance_sum += distance[start][end]
}
return N / distance_sum
}
Cross.js
const POP_SIZE = require('./init/const').POP_SIZE
const CITY_NUM = require('./init/const').CITY_NUM
const N = require('./init/const').N
const fs = require('fs')
module.exports = function(city,distance) {
let father = city.colony[ city.SelectParent[0] ]
let mother = city.colony[ city.SelectParent[1] ]
let r = Math.floor(Math.random() * 5) + 1
let son1 = []
let son2 = []
let son1_j = 0
let son2_j = 0
if( (r + 4) <= (CITY_NUM - 1) ) {
for(let i = 0; i < CITY_NUM; i ++) {
if(i >= r && i <= r + 4){
son1[son1_j ++] = father[i]
son2[son2_j ++] = mother[i]
}
}
}
mother.forEach((ele,index) => {
let flag = 1
for(let i = 0; i < son1.length; i ++) {
if(ele == son1[i])
flag = 0
}
if(flag == 1 && ele != 0) {
if(index < r)
son1.unshift(mother[index])
else
son1.push(mother[index])
}
})
father.forEach((ele,index) => {
let flag = 1
for(let i = 0; i < son2.length; i ++) {
if(ele == son2[i])
flag = 0
}
if(flag == 1 && ele != 0) {
if(index < r)
son2.unshift(father[index])
else
son2.push(father[index])
}
})
son1.unshift(0)
son1.push(0)
son2.unshift(0)
son2.push(0)
let son1_distance = 0
let son2_distance = 0
for(let i = 0; i < CITY_NUM; i ++) {
son1_distance += distance[ son1[i] ][ son1[i + 1] ]
son2_distance += distance[ son2[i] ][ son2[i + 1] ]
}
let son1_fitness = N / son1_distance
let son2_fitness = N / son2_distance
let flag1 = 0
let flag2 = 0
for(let k = 0; k < POP_SIZE; k ++) {
if(flag1 == 0 && city.fitness[k] < son1_fitness) {
city.fitness[k] = son1_fitness
city.colony[k] = son1
city.Distance[k] = son1_distance
flag1 = 1
}else if(flag2 == 0 && city.fitness[k] < son2_fitness) {
city.fitness[k] = son2_fitness
city.colony[k] = son2
city.Distance[k] = son2_distance
flag2 = 1
}
}
}
Copy
module.exports = function(arr2,arr1) {
let i = 0
for(let ele of arr1) {
arr2[i++] = ele
}
}
init文件夹
const.js
module.exports = {
POP_SIZE:500,
MAX_VALUE:10000000,
CITY_NUM:10,
N:100000,
MAX_EPOC:2,
COUNT:1,
}
initColony.js
const check = require('./Check.js')
const POPSIZE = require('./const').POP_SIZE
const MAXVALUE = require('./const').MAX_VALUE
const CITY_NUM = require('./const').CITY_NUM
module.exports = function(city,tsp_data) {
for(var i = 0; i < POPSIZE; i ++) {
if(!city.colony[i])
city.colony[i] = []
city.colony[i][0] = 0
city.colony[i][CITY_NUM] = 0
city.BestIndex = MAXVALUE
city.BestFitness = 0
}
for(var i = 0; i < POPSIZE; i ++) {
for(var j = 1; j < CITY_NUM; j ++) {
let r = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
while(check(city,i,j,r))
{
r = Math.floor(Math.random() * (CITY_NUM - 1)) + 1
}
city.colony[i][j] = r;
}
}
}
cityDistance.js
module.exports = function(tsp_data) {
let res = []
for(let i in tsp_data) {
for(let j in tsp_data) {
const x = tsp_data[i].x - tsp_data[j].x
const y = tsp_data[i].y - tsp_data[j].y
if(!res[i])
res[i] = []
res[i][j] = Math.sqrt(x*x + y*y)
}
}
console.log("最短路径序列:0 3 5 4 9 8 7 6 2 1 0")
console.log("最短路径长:",res[0][3] + res[3][5] + res[5][4] + res[4][9] + res[9][8] + res[8][7] + res[7][6] + res[6][2] + res[2][1] + res[1][0])
return res
}
CalFitness.js
const Copy = require('../Copy.js')
const POP_SIZE = require('./const').POP_SIZE
const CITY_NUM = require('./const').CITY_NUM
const N = require('./const').N
module.exports = function(city,distance) {
let start,end
let best = 0
for(let i = 0; i < POP_SIZE; i ++) {
city.Distance[i] = 0
for(let j = 0; j < CITY_NUM; j ++) {
start = city.colony[i][j]
end = city.colony[i][j + 1]
city.Distance[i] += distance[start][end]
}
city.fitness[i] = N / city.Distance[i]
if(city.fitness[i] > city.fitness[best])
best = i
}
Copy(city.BestRooting,city.colony[best])
city.BestFitness = city.fitness[best]
city.BestPath = city.Distance[best]
city.BestIndex = best
}
Check.js
const CITYNUM = require('./const').CITYNUM
module.exports = function(city,pop,num,r) {
for(var i = 0; i <= num; i ++) {
if(r == city.colony[pop][i])
return true
}
return false
}