利用遗传算法解决TSP问题及可视化程序
- 实验目的
- 掌握遗传算法在实际问题中的应用方法
- 理解遗传算法中染色体编码和适应度函数的定义方法
- 熟悉遗传算法中选择、交叉、变异操作在实际问题中的适配问题
- 熟悉遗传算法中的控制参数设置的性能的影响
- 将解决方案可视化并建立和用户交互前端程序
- 实验内容
在本次实验中,我们给定了中国34个省会城市的二维坐标,其中部分数据截图如下:
数据总共由34行组成,每一行代表一个城市名字以及对应坐标。两个城市之间的距离可以通过对应坐标求欧氏距离得到。对于给定数据,要求选择始发城市和剩余33个城市中的全部城市或部分城市作为需要遍历的城市,通过编写相应的遗传算法代码,求解TSP问题中回到始发城市的路径,并且尽可能的使路径总长度最短。
图1.地图可视化
设计实现相应的前端可视化程序。例如,34个城市的可视化结果如图1左图所示。两个城市之间的距离可以通过两个城市的坐标来求出,假设在旅行商问题中,旅行商希望经过的城市为乌鲁木齐、银川、拉萨、重庆这四个城市并且起点为重庆,那么一个可行的解的可视化结果如图1右图所示。设计的软件程序中包含类似的地图可视化。
三、实验方法设计
介绍实验中程序的总体设计方案、关键步骤的编程方法及思路,主要包括:
染色体编码和适应度定义的程序设计(伪代码或源代码截图)及说明解释
染色体编码定义:用一个列表表示,列表的第一个和最后一个元素为起始城市的序号,中间元素为途经城市的序号,按照路径的顺序排列
适应度定义:该路径的距离总和的倒数,用列表表示一个种群中的个体适应度(路径越长,其倒数越小,适应度越低)
种群初始化程序设计(伪代码或源代码截图)及说明解释
一个种群为一个二维列表,每一行代表一个个体,共有M(种群数量)列,每个个体的染色体用路径顺序的列表表示
rand函数参数为起始城市和途经城市,返回一个列表,其中列表的第一个元素和最后一个元素为起始城市,中间城市随机排列
选择操作程序设计(伪代码或源代码截图)及说明解释
用轮盘赌进行选择,一共选择M次(种群个体数目)
RWS算法的输入为每个个体被选择的概率(适应度越高,被选择概率越高),输出为选择的个体序号
交叉操作程序设计(伪代码或源代码截图)及说明解释
交叉操作主要是将输入的群体进行片段的交换,我的交叉算法参考了网上的次序杂交算法。
次序杂交算法首先随机地在双亲中选择两个杂交点,再交换杂交段,其他 位置根据双亲城市的相对位置确定。例如:A1[10]={0, 8,5,4,7,3,1,6,9,2},A2[10]={1,5,4,6,9,0,3,2,8,7}, 随机杂交点为 4,7。 首先交换杂交段: B1[10]={&,&,&,&|9,0,3,2|&,&} B2[10]={&,&, &,&|7,3,1,6|&,&}, 从 A1 的第二个杂交点开始 9— 2—0—8—5—4—7—3—1—6,去除杂交段中的元素得 8—5—4—7—1—6, 依次从第二个杂交点开始填入得 B1[10]={4,7,1,6,9,0,3,2,8,5},同理得 B2[10]={4,9, 0,2,7,3,1,6,8,5}。
但是即使用此交叉算法优化出的最短路线长度仍为370左右,因此我在交叉时产生了种群数M二倍的子代,再从子代中选出M个相对于较优的个体。这种方式将最短路径降低为170左右,虽然不是十分符合遗传算法的规律。
交叉中选用移除初始城市序号的列表。生成的子代再进一步选择:
子函数如下所示:
变异操作程序设计(伪代码或源代码截图)及说明解释
变异操作采用了十分简单的方法:随机选择两个城市序号(除初始城市)进行交换
子函数如下:输入为变异概率和变异种群,输出为已变异种群
四、实验结果展示
展示程序界面设计、运行结果及相关分析等,主要包括:
可视化程序界面展示及各功能组件介绍:
首先输入起始城市的序号,然后输入途经城市序号,每一个序号中间用空格分离,如果输入为空(直接回车),则为所有城市
运行结果如下所示:
- 遗传算法收敛图(适应度值随迭代增加的变化趋势)
当途径所有城市时,遗传算法收敛图如下,其中横坐标为迭代轮数,纵坐标为本轮最高适应度,可以从图中看到,适应度随着迭代轮数逐渐升高并趋于平缓。
- 不同城市组合下的最优结果城市路径展示及分析
- 可以看到结果接近最短路线
可以看到结果是正确的最短路线
不同初始种群个数对结果的影响分析
初始种群个数越多,运行越慢,结果越能接近距离最小值。但是不能过大,过大会导致运行过慢,且在计算最小路径上比适当种群数目只有较小影响
不同交叉概率和变异概率对结果的影响分析
交叉的作用为保证种群的稳定性,朝着最优解的方向进化,因此交叉的概率应当适度大一些,如果交叉概率过低,可能得不到最优解
变异的作用为保证种群的多样性,避免交叉可能产生的局部收敛,变异概率应小一点,过大或过小可能会找不到最优解
test.py
import random
import math
import copy
import matplotlib.pyplot as plt
#初始化种群
def rand(n,passby):
seq = []
seq.append(n)
length = len(passby)
i = 0
while i<length:
k = random.randint(0,length-1)
try:
seq.index(passby[k])
except:
seq.append(passby[k])
i += 1
seq.append(n)
return seq
def culDistance(N,seq1,cities):
distance = [0]*N
for i in range(N):
for j in range(1,len(seq1[0])):
city1 = seq1[i][j-1]
city2 = seq1[i][j]
distance[i] += (math.sqrt(pow(float(cities[city1][2])-float(cities[city2][2]),2)+pow(float(cities[city1][1])-float(cities[city2][1]),2)))
return distance
#轮盘赌算法
def RWS(p):
m = 0
r = random.random() #产生随机数[0,1)
for i in range(len(p)):
m += p[i]
if r<=m:
return i
#交换(用于选择运算)
def change(seq,select):
temp = []
length = len(seq)
for i in range(length):
temp.append(seq[select[i]])
for i in range(length):
seq[i] = temp[i]
return seq
#交叉繁殖
def crossover(parents):
length = len(parents)
#孩子列表
children=[]
while len(children)<2*length:
male_index = random.randint(0, len(parents) - 1)
female_index = random.randint(0, len(parents) - 1)
if male_index!=female_index:
male=parents[male_index]
female=parents[female_index]
left=random.randint(0,len(male)-2)
right=random.randint(left+1,len(male)-1)
#交叉片段
gene1=male[left:right]
gene2=female[left:right]
child1_c=male[right:]+male[:right]
child2_c=female[right:]+female[:right]
child1=child1_c.copy()
child2=child2_c.copy()
for o in gene2:
child1_c.remove(o)
for o in gene1:
child2_c.remove(o)
child1[left:right]=gene2
child2[left:right]=gene1
child1[right:]=child1_c[0:len(child1)-right]
child1[:left] = child1_c[len(child1) - right:]
child2[right:] = child2_c[0:len(child1) - right]
child2[:left] = child2_c[len(child1) - right:]
children.append(child1)
children.append(child2)
return children
#变异
def variation(children,variation_p):
length = len(children)
lengthcity = len(children[0])
for i in range(length):
if random.random() < variation_p:
m = random.randint(0,lengthcity-1)
n = random.randint(0,lengthcity-1)
while m==n:
m = random.randint(0,lengthcity-1)
n = random.randint(0,lengthcity-1)
children[i][n],children[i][m] = children[i][m],children[i][n]
#读文件中的城市信息并存储到数组当中
def readfile():
file = open("data.txt")
context = file.readlines()
cities = []
length = len(context) #城市数目
for i in range(length):
cities.append(context[i].rstrip().split(","))
file.close()
return cities
def result(begin,passby):
#读文件
cities = readfile()
#初始化
t = 0 #进化代数计数器
T = 500#最大进化代数
M = 50#种群规模
cross_p = 0.8
variation_p = 0.2
parents = [] #种群个体
fitness = [] #自适应度
distance = []
select_p = []#选择概率
plt.figure(figsize=(12, 10), dpi=80)
X = []
Y = []
#生成初始种群
for i in range(M):
parents.append(rand(begin,passby))
for t in range(T):
# 计算总距离
distance = []
distance = culDistance(M,parents,cities)
distance.sort()
#计算自适应度
fitness = []
for i in range(M):
fitness.append(1/distance[i])
#print("最高适应度为",max(fitness))
X.append(t)
Y.append(max(fitness))
#计算选择概率
select_p = []
for i in range(M):
select_p.append(fitness[i]/sum(fitness))
#进行轮盘赌
select = []
for i in range(M):
select.append(RWS(select_p))
#交换个体
parents = change(parents,select)
#交叉
for i in range(M):
parents[i] = copy.deepcopy(parents[i][1:len(parents[i])-1])
children = []
p = random.random()
if p <= cross_p:
children = crossover(parents)
else:
children = copy.deepcopy(parents)
children = children*2
#
childDistance= []
childDistance = culDistance(2*M,children,cities)
for i in range(M):
n = childDistance.index(max(childDistance))
childDistance.pop(n)
children.pop(n)
#
#变异
variation(children,variation_p)
#加上起点
for i in range(M):
children[i] = [begin]+children[i]+[begin]
parents = copy.deepcopy(children)
if t==T-1:
distance = []
plt.plot(X,Y,'-o')
distance = culDistance(M,parents,cities)
return distance,parents
#distance,seq = result(0,list(range(1,34)))
#print(seq)
#print(distance)
#city,d,seq = result(0)
#print(d)
#print(seq)
可视化界面:
# -*- coding: utf-8 -*-
import test
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
cities = test.readfile()
for i in range(len(cities)):
print(i,cities[i][0],end="")
begin = int(input("请输入起始城市:"))
passby = input(" 请输入途经城市:(以空格分离)")
if passby.rstrip()=="":
passby = list(range(0,34))
passby.remove(begin)
else:
passby = passby.rstrip().split(" ")
for i in range(len(passby)):
passby[i] = int(passby[i])
print(passby)
distance,seq = test.result(begin,passby)
route = []
shortest = min(distance)
n = distance.index(shortest)
route = seq[n]
print("最短路线为:",route)
print("距离为:",shortest)
X = []
Y = []
plt.figure(figsize=(12, 10), dpi=80)
for i in range(len(route)):
X.append(float(cities[route[i]][1]))
Y.append(float(cities[route[i]][2]))
plt.text(X[i],Y[i],cities[route[i]][0])
plt.plot(X,Y,'-o')
总结:当遍历全部城市时,达不到最优解,只能靠近,最小试过的为170左右