旅行商问题(Travelling salesman problem, TSP):
一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。
遗传算法(Genetic Algorithm):
模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,通过模拟自然进化过程搜索最优解。遗传算法是从一组候选解构成的一个种群(population)开始的,初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择个体,并借助于的遗传算子进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。这个过程将导致种群按照自然进化一样产生后生代种群,并且更适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。
如图所示,在遗传算法中,一共有如下几个步骤:
- 初始化:设置进化代数计数器 t=0、设置最大进化代数 T、交叉概率、变 异概率、随机生成 M 个个体作为初始种群 P
- 个体评价:计算种群 P 中各个个体的适应度
- 选择运算:将选择算子作用于群体。以个体适应度为基础,选择最优个体 直接遗传到下一代或通过配对交叉产生新的个体再遗传到下一代
- 交叉运算:在交叉概率的控制下,对群体中的个体两两进行交叉
- 变异运算:在变异概率的控制下,对群体中的个体两两进行变异,即对某 一个体的基因进行随机调整
- 经过选择、交叉、变异运算之后得到下一代群体 P’
算法实现(python)
(其中只要运行TSP_GA.py即可以运行终端的版本,运行TSP_GA_app.py为GUI版本)
GA.py遗传算法类
import random
from Life import Life
#遗传算法类
class GA(object):
def __init__(self, aCrossRate, aMutationRate, aLifeCount, aGeneLength, aMatchFun = lambda life : 1):
self.crossRate = aCrossRate #交叉概率
self.mutationRate = aMutationRate #突变概率
self.lifeCount = aLifeCount #种群数量,就是每次我们在多少个城市序列里筛选,这里初始化为100
self.geneLength = aGeneLength #其实就是城市数量
self.matchFun = aMatchFun #适配函数
self.lives = [] #种群
self.best = None #保存这一代中最好的个体
self.generation = 1 #一开始的是第一代
self.crossCount = 0 #一开始还没交叉过,所以交叉次数是0
self.mutationCount = 0 #一开始还没变异过,所以变异次数是0
self.bounds = 0.0 #适配值之和,用于选择时计算概率
self.initPopulation() #初始化种群
def initPopulation(self):
"""初始化种群"""
self.lives = []
for i in range(self.lifeCount):
#gene = [0,1,…… ,self.geneLength-1]
#事实就是0到33
gene = list(range(self.geneLength))
#将0到33序列的所有元素随机排序得到一个新的序列
random.shuffle(gene)
#Life这个类就是一个基因序列,初始化life的时候,两个参数,一个是序列gene,一个是这个序列的初始适应度值
#因为适应度值越大,越可能被选择,所以一开始种群里的所有基因都被初始化为-1
life = Life(gene)
#把生成的这个基因序列life填进种群集合里
self.lives.append(life)
def judge(self):
"""评估,计算每一个个体的适配值"""
#适配值之和,用于选择时计算概率
self.bounds = 0.0
#假设种群中的第一个基因被选中
self.best = self.lives[0]
for life in self.lives:
life.score = self.matchFun(life)
self.bounds += life.score
#如果新基因的适配值大于原先的best基因,就更新best基因
if self.best.score < life.score:
self.best = life
def cross(self, parent1, parent2):
"""交叉"""
index1 = random.randint(0, self.geneLength - 1)
index2 = random.randint(index1, self.geneLength - 1)
tempGene = parent2.gene[index1:index2] #交叉的基因片段
newGene = []
p1len = 0 #记录位置
for g in parent1.gene:
if p1len == index1:
newGene.extend(tempGene) #插入基因片段
p1len += 1
if g not in tempGene:
newGene.append(g)
p1len += 1
self.crossCount += 1
return newGene
def mutation(self, gene):
"""突变"""
#相当于取得0到self.geneLength - 1之间的一个数,包括0和self.geneLength - 1
index1 = random.randint(0, self.geneLength - 1)
index2 = random.randint(0, self.geneLength - 1)
#把这两个位置的城市互换
gene[index1], gene[index2] = gene[index2], gene[index1]
#突变次数加1
self.mutationCount += 1
return gene
def getOne(self):
"""选择一个个体"""
#产生0到(适配值之和)之间的任何一个实数
r = random.uniform(0, self.bounds)
for life in self.lives:
r -= life.score
if r <= 0:
return life
raise Exception("选择错误", self.bounds)
def newChild(self):
"""产生新后的"""
#选择
parent1 = self.getOne()
rate = random.random()
#按概率交叉
if rate < self.crossRate:
parent2 = self.getOne()
gene = self.cross(parent1, parent2)
else:
gene = parent1.gene
#按概率突变
rate = random.random()
if rate < self.mutationRate:
gene = self.mutation(gene)
return Life(gene)
def next(self):
"""产生下一代"""
self.judge()#评估,计算每一个个体的适配值
newLives = []
newLives.append(self.best)#把最好的个体加入下一代
while len(newLives) < self.lifeCount:
newLives.append(self.newChild())
self.lives = newLives
self.generation += 1
Life.py基因序列类
SCORE_NONE = -1
#个体类
class Life(object):
def __init__(self, aGene = None):
self.gene = aGene
self.score = SCORE_NONE
TSP.py旅行商算法类
import math
from GA import GA
class TSP(object):
def __init__(self, aLifeCount = 100):
self.initCitys()
self.lifeCount = aLifeCount
self.ga = GA(aCrossRate = 0.7,
aMutationRate = 0.02,
aLifeCount = self.lifeCount,
aGeneLength = len(self.citys),
aMatchFun = self.matchFun())
def initCitys(self):
self.citys = []
#这个文件里是34个城市的经纬度
f = open("data.txt","r")
for line in f.readlines():
line = line.strip()
line = line.split(',')
#中国34城市经纬度读入citys
self.citys.append((float(line[1]),float(line[2]),line[0]))
f.close()
#order是遍历所有城市的一组序列,如[1,2,3,7,6,5,4,8……]
#distance就是计算这样走要走多长的路
def distance(self, order):
distance = 0.0
#i从-1到32,-1是倒数第一个
for i in range(-1, len(self.citys) - 1):
index1, index2 = order[i], order[i + 1]
city1, city2 = self.citys[index1], self.citys[index2]
distance += math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2)
return distance
#适应度函数,因为我们要从种群中挑选距离最短的,作为最优解,所以(1/距离)最长的就是我们要求的
def matchFun(self):
return lambda life: 1.0 / self.distance(life.gene)
def run(self, n = 0):
while n > 0:
self.ga.next()
distance = self.distance(self.ga.best.gene)
print (("%d : %f") % (self.ga.generation, distance))
print (self.ga.best.gene)
n -= 1
print("经过%d次迭代,最优解距离为:%f"%(self.ga.generation, distance))
print("遍历城市顺序为:",)
#print "遍历城市顺序为:", self.ga.best.gene
#打印出我们挑选出的这个序列中
for i in self.ga.best.gene:
print(self.citys[i][2],)
TSP_GA.py
from TSP import TSP
import tkinter
def main():
tsp = TSP()
tsp.run(500)
if __name__ == '__main__':
main()
TSP_app.py旅行商算法类(GUI)
import math
from GA import GA
import random
class TSP(object):
def __init__(self,xuan_citys,aLifeCount = 100):
self.lifeCount = aLifeCount
self.xuan_citys = xuan_citys
self.initCitys()
self.ga = GA(aCrossRate = 0.7,
aMutationRate = 0.02,
aLifeCount = self.lifeCount,
aGeneLength = len(self.citys),
aMatchFun = self.matchFun())
def initCitys(self):
self.citys = []
#这个文件里是34个城市的经纬度
f = open("data.txt","r")
for line in f.readlines():
line = line.strip()
line = line.split(',')
#中国34城市经纬度读入citys
self.citys.append((float(line[1]),float(line[2]),line[0]))
f.close()
new_node = []
for i in self.citys:
for j in self.xuan_citys:
if j in i:
new_node.append(i)
self.citys = new_node
#order是遍历所有城市的一组序列,如[1,2,3,7,6,5,4,8……]
#distance就是计算这样走要走多长的路
def distance(self, order):
distance = 0.0
#i从-1到32,-1是倒数第一个
for i in range(-1, len(self.citys) - 1):
index1, index2 = order[i], order[i + 1]
city1, city2 = self.citys[index1], self.citys[index2]
distance += math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2)
return distance
#适应度函数,因为我们要从种群中挑选距离最短的,作为最优解,所以(1/距离)最长的就是我们要求的
def matchFun(self):
return lambda life: 1.0 / self.distance(life.gene)
def run(self,n,m):
lis_best =[]
lis_dis = []
ch = []
#随机选取m-1组值
for i in range(m-1):
k = random.randint(1,n)
if k not in ch:
ch.append(k)
while n > 0:
self.ga.next()
distance = self.distance(self.ga.best.gene)
if n in ch:
lis_best.append(self.ga.best.gene)
lis_dis.append(distance)
if n == 1:
lis_best.append(self.ga.best.gene)
lis_dis.append(distance)
#print (("%d : %f") % (self.ga.generation, distance))
#print (self.ga.best.gene)
n -= 1
#print("经过%d次迭代,最优解距离为:%f"%(self.ga.generation, distance))
#print("遍历城市顺序为(最后一组):")
#打印出我们挑选出的这个序列中
for i in self.ga.best.gene:
print(self.citys[i][2],end=' ')
print("以上是最优解")
#返回城市列表
nodes = []
for i in lis_best:
node = []
for j in i:
node.append(self.citys[j])
nodes.append(node)
return nodes,True,lis_dis
TSP_GA_app.py基于tkinter的GUI实现部分
from TSP_app import TSP
from tkinter import *
citys = []
f = open("data.txt","r")
for line in f.readlines():
line = line.strip()
line = line.split(',')
citys.append((float(line[1]),float(line[2]),line[0]))
f.close()
minX = citys[0][0]
minY = citys[0][1]
maxX, maxY = minX, minY
for city in citys[1:]:
if minX > city[0]:
minX = city[0]
if minY > city[1]:
minY = city[1]
if maxX < city[0]:
maxX = city[0]
if maxY < city[1]:
maxY = city[1]
#基本画板大小 560 330
pw = 560
ph = 330
#最大的横纵坐标差
w = maxX - minX
h = maxY - minY
#起始点选择
xoffset = 60
yoffset = 30
#伸缩比例
tx = (pw - 2*xoffset)/float(w)
ty = (ph - 2*yoffset)/float(h)
#坐标映射,nodes是34个城市位置的映射
nodes = []
for city in citys:
x = (city[0] - minX ) * tx + xoffset
y = (ph - 2*yoffset) - (city[1] - minY) * ty + yoffset
nodes.append((x, y,city[2]))
app = Tk()
app.title("TSP")
app.geometry("740x600")
canvas = Canvas(app,width=560,height=330,bg="BlanchedAlmond")
canvas.place(x=168,y=0,anchor='nw')
#中国34城市经纬度读入citys,不管选择几个城市,先全部标点
for i in nodes:
canvas.create_text((i[0],i[1]),text=i[2],fill="black",font=("Arial",8))
#中间分割框
Label(app,width=1,height=600,bg='green').place(x=152,y=0,anchor='nw')
#左下方输出城市
text1=Text(app,height=18,width=20,bg="silver")
text1.place(x=5,y=351,anchor='nw')
#右下方输出计算后的城市
text2=Text(app,height=13,width=80,bg="silver")
text2.place(x=168,y=420,anchor='nw')
allCities=['北京','天津','上海','重庆','拉萨','乌鲁木齐','银川','呼和浩特','南宁','哈尔滨','长春','沈阳','石家庄','太原','西宁','济南','郑州','南京','合肥','杭州','福州','兰州','南昌','长沙','武汉','广州','台北','海口','西安','成都','贵阳','昆明','香港','澳门']
#记录选择的城市
list_cities=[]
def insert_City(city):
if city not in list_cities:
list_cities.append(city)
text1.insert(INSERT,city+'->')
else :
list_cities.remove(city)
text1.insert(INSERT,"%s(删除)"%city+'->')
def insert_all():
for i in allCities:
if i not in list_cities:
list_cities.append(i)
text1.insert(INSERT,i+'->')
Button(app,text="北京", width=3, height=1, command=lambda:insert_City("北京")).place(x=10,y=1,anchor='nw')
Button(app,text="天津", width=3, height=1, command=lambda:insert_City("天津")).place(x=45,y=1,anchor='nw')
Button(app,text="上海", width=3, height=1, command=lambda:insert_City("上海")).place(x=80,y=1,anchor='nw')
Button(app,text="重庆", width=3, height=1, command=lambda:insert_City("重庆")).place(x=115,y=1,anchor='nw')
Button(app,text="拉萨", width=3, height=1, command=lambda:insert_City("拉萨")).place(x=10,y=31,anchor='nw')
Button(app,text="银川", width=3, height=1, command=lambda:insert_City("银川")).place(x=45,y=31,anchor='nw')
Button(app,text="长春", width=3, height=1, command=lambda:insert_City("长春")).place(x=80,y=31,anchor='nw')
Button(app,text="沈阳", width=3, height=1, command=lambda:insert_City("沈阳")).place(x=115,y=31,anchor='nw')
Button(app,text="南宁", width=3, height=1, command=lambda:insert_City("南宁")).place(x=10,y=61,anchor='nw')
Button(app,text="太原", width=3, height=1, command=lambda:insert_City("太原")).place(x=45,y=61,anchor='nw')
Button(app,text="西宁", width=3, height=1, command=lambda:insert_City("西宁")).place(x=80,y=61,anchor='nw')
Button(app,text="济南", width=3, height=1, command=lambda:insert_City("济南")).place(x=115,y=61,anchor='nw')
Button(app,text="郑州", width=3, height=1, command=lambda:insert_City("郑州")).place(x=10,y=91,anchor='nw')
Button(app,text="南京", width=3, height=1, command=lambda:insert_City("南京")).place(x=45,y=91,anchor='nw')
Button(app,text="合肥", width=3, height=1, command=lambda:insert_City("合肥")).place(x=80,y=91,anchor='nw')
Button(app,text="杭州", width=3, height=1, command=lambda:insert_City("杭州")).place(x=115,y=91,anchor='nw')
Button(app,text="福州", width=3, height=1, command=lambda:insert_City("福州")).place(x=10,y=121,anchor='nw')
Button(app,text="南昌", width=3, height=1, command=lambda:insert_City("南昌")).place(x=45,y=121,anchor='nw')
Button(app,text="长沙", width=3, height=1, command=lambda:insert_City("长沙")).place(x=80,y=121,anchor='nw')
Button(app,text="武汉", width=3, height=1, command=lambda:insert_City("武汉")).place(x=115,y=121,anchor='nw')
Button(app,text="广州", width=3, height=1, command=lambda:insert_City("广州")).place(x=10,y=151,anchor='nw')
Button(app,text="台北", width=3, height=1, command=lambda:insert_City("台北")).place(x=45,y=151,anchor='nw')
Button(app,text="海口", width=3, height=1, command=lambda:insert_City("海口")).place(x=80,y=151,anchor='nw')
Button(app,text="兰州", width=3, height=1, command=lambda:insert_City("兰州")).place(x=115,y=151,anchor='nw')
Button(app,text="西安", width=3, height=1, command=lambda:insert_City("西安")).place(x=10,y=181,anchor='nw')
Button(app,text="成都", width=3, height=1, command=lambda:insert_City("成都")).place(x=45,y=181,anchor='nw')
Button(app,text="贵阳", width=3, height=1, command=lambda:insert_City("贵阳")).place(x=80,y=181,anchor='nw')
Button(app,text="昆明", width=3, height=1, command=lambda:insert_City("昆明")).place(x=115,y=181,anchor='nw')
Button(app,text="香港", width=3, height=1, command=lambda:insert_City("香港")).place(x=10,y=211,anchor='nw')
Button(app,text="乌鲁木齐",width=6,height=1, command=lambda:insert_City("乌鲁木齐")).place(x=45,y=211,anchor='nw')
Button(app,text="哈尔滨", width=4,height=1, command=lambda:insert_City("哈尔滨")).place(x=108,y=211,anchor='nw')
Button(app,text="澳门",width=3,height=1, command=lambda:insert_City("澳门")).place(x=10,y=241,anchor='nw')
Button(app,text="呼和浩特",width=6,height=1, command=lambda:insert_City("呼和浩特")).place(x=45,y=241,anchor='nw')
Button(app,text="石家庄", width=4,height=1, command=lambda:insert_City("石家庄")).place(x=108,y=241,anchor='nw')
Button(app,text="全选", width=3, height=1,bg="pink", command=insert_all).place(x=115,y=271,anchor='nw')
Label(app,text="选择的城市", width=18, height=1,bg="yellow").place(x=10,y=321,anchor='nw')
Label(app,text="生成", width=3, height=1).place(x=168,y=340,anchor='nw')
num_Entry1 = Entry(app,width=3)
num_Entry1.place(x=198,y=340,anchor='nw')
Label(app,text="组", width=1, height=1).place(x=225,y=340,anchor='nw')
Label(app,text="遗传", width=3, height=1).place(x=260,y=340,anchor='nw')
num_Entry2 = Entry(app,width=7)
num_Entry2.place(x=290,y=340,anchor='nw')
Label(app,text="代", width=1, height=1).place(x=340,y=340,anchor='nw')
#连线可视化
def draw(ch_citys):
#筛选出选择的城市并连线
new_nodes = []
for i in ch_citys:
for j in nodes:
if j[2] == i[2]:
new_nodes.append(j)
continue
new_nodes.append(new_nodes[0])
#第一个点坐标
ox = new_nodes[0][0]
oy = new_nodes[0][1]
for i in new_nodes[1:]:
canvas.create_line((ox,oy),(i[0],i[1]),width=1,fill="blue")
ox = i[0]
oy = i[1]
#获取迭代次数
def start():
generate_time=num_Entry2.get()
gene_num = num_Entry1.get()
tsp = TSP(list_cities)
ch_citys,flag,dis = tsp.run(int(generate_time),int(gene_num))
if flag:
draw(ch_citys[int(gene_num)-1])
text2.insert(INSERT,'遍历城市顺序为:\n')
for m in range(int(gene_num)):
text2.insert(INSERT,'\n'+str(float(int(m)+1))+':最优解距离为:'+str(dis[m])+'\n')
for i in ch_citys[m]:
text2.insert(INSERT,i[2]+' ')
Button(app,text="开始", width=3, height=1,command=start).place(x=168,y=380,anchor='nw')
app.mainloop()
data.txt城市数据
北京,116.46,39.92
天津,117.2,39.13
上海,121.48,31.22
重庆,106.54,29.59
拉萨,91.11,29.97
乌鲁木齐,87.68,43.77
银川,106.27,38.47
呼和浩特,111.65,40.82
南宁,108.33,22.84
哈尔滨,126.63,45.75
长春,125.35,43.88
沈阳,123.38,41.8
石家庄,114.48,38.03
太原,112.53,37.87
西宁,101.74,36.56
济南,117,36.65
郑州,113.6,34.76
南京,118.78,32.04
合肥,117.27,31.86
杭州,120.19,30.26
福州,119.3,26.08
南昌,115.89,28.68
长沙,113,28.21
武汉,114.31,30.52
广州,113.23,23.16
台北,121.5,25.05
海口,110.35,20.02
兰州,103.73,36.03
西安,108.95,34.27
成都,104.06,30.67
贵阳,106.71,26.57
昆明,102.73,25.04
香港,114.1,22.2
澳门,113.33,22.13
GUI大致布局![2(https://img-blog.csdnimg.cn/2020010809270459.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MDQzMTkyMQ==,size_16,color_FFFFFF,t_70)
运行结果(GUI版本)
初始界面
结果界面
心得体会
遗传算法的每次运行结果是不一致的,感觉要得到一个最优的结果,编写代码是其一,关键参数的设置也是不可忽略的。难点在于染色体编码、适应度定义,而运行代数、种群数量、变异概率、交叉概率的影响也不小。我最开始运行的几组数据大都在200多,偶尔在200以内。后来改小了交叉概率,运行结果基本在180以内了。因此参数设置也是及其重要的。
参考链接:
https://github.com/chaolongzhang/tsp
https://blog.csdn.net/wjciayf/article/details/79261005
https://www.runoob.com/python/python-gui-tkinter.html
https://blog.csdn.net/Jin_Kwok/article/details/80147263