1. 前言
上一篇博文【五种常见启发式算法求解TSP问题-总结篇】中,总结了五种常见启发式算法在求解TSP问题上的效果,其中遗传算法的求解质量最差,而粒子群算法和禁忌搜索算法的求解效果最佳,因此本文计划引入粒子群思想和禁忌搜索思想对遗传算法进行优化。
2. 算法设计
2.1 遗传-禁忌搜索算法
2.1.1 改进思路
禁忌搜索算法的核心思想是不重复已经搜索过的解,以提高搜索的效率。在改进遗传算法上,可以对交叉算子和变异算法做出禁忌操作,具体改进想法如下:
①建立全局禁忌表,存储每一代中的最优解,在所有交叉和变异操作中作为禁忌对象;
②建立局部禁忌表,在交叉算子中,局部禁忌表为上一代种群和当前已交叉生成的子代种群,在变异算子中,局部禁忌表为上一代种群、交叉后待变异的种群和当前已变异生成子代种群;
2.1.2 程序设计
①基于禁忌搜索算法改进的交叉算子
def crossover(popsize,parent1_pops,parent2_pops,pc,tabu_list,par_pops):
'''
#顺序交叉
输入:popsize-种群大小,parent1_pops-父代,parent2_pops-母代,pc-交叉概率,tabu_list-全局禁忌表,par_pops-上一代种群;
输出:交叉后的种群-child_pops
'''
child_pops = []
for i in range(popsize):
flag = True
while flag:
#初始化
child = [None]*len(parent1_pops[i])
parent1 = parent1_pops[i]
parent2 = parent2_pops[i]
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
#parent1 -> child
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
# parent2 -> child
list1 = list(range(end_pos+1,len(parent2)))
list2 = list(range(0,start_pos))
list_index = list1+list2
j = -1
for i in list_index:
for j in range(j+1,len(parent2)):
if parent2[j] not in child:
child[i] = parent2[j]
break
if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#判断是否满足禁忌表
child_pops.append(child)
flag = False
return child_pops
②基于禁忌搜索算法改进的变异算子
def mutate(pops,pm,tabu_list,par_pops):
'''
#基本位变异,成对变异
输入:pops-交叉后种群,pm-变异概率,tabu_list-群居禁忌表,par_pops-上一代种群;
输出:变异后种群-pops_mutate
'''
pops_mutate = []
for i in range(len(pops)):
flag = True
while flag:
pop = pops[i].copy()
t = random.randint(1,10)#随机多次成对变异
count = 0
while count < t:
if random.random() < pm:
mut_pos1 = random.randint(0,len(pop)-1)
mut_pos2 = random.randint(0,len(pop)-1)
if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
count +=1
if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、交叉后种群禁忌,上一代种群禁忌
pops_mutate.append(pop)
flag = False
return pops_mutate
③完整程序
# -*- coding: utf-8 -*-
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 添加这条可以让图形显示中文
def calFitness(line,dis_matrix):
'''
计算路径距离,即评价函数
输入:line-路径,dis_matrix-城市间距离矩阵;
输出:路径距离-dis_sum
'''
dis_sum = 0
dis = 0
for i in range(len(line)-1):
dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
dis_sum = dis_sum+dis
dis = dis_matrix.loc[line[-1],line[0]]
dis_sum = dis_sum+dis
return round(dis_sum,1)
def tournament_select(pops,popsize,fits,tournament_size):
'''
#锦标赛选择
'''
new_pops = []
while len(new_pops)<len(pops):
tournament_list = random.sample(range(0,popsize),tournament_size)
tournament_fit = [fits[i] for i in tournament_list]
#转化为df方便索引
tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
#选出获胜者
pop = pops[int(tournament_df.iloc[0,0])]
new_pops.append(pop)
return new_pops
def crossover(popsize,parent1_pops,parent2_pops,pc,tabu_list,par_pops):
'''
#顺序交叉
输入:popsize-种群大小,parent1_pops-父代,parent2_pops-母代,pc-交叉概率,tabu_list-全局禁忌表,par_pops-上一代种群;
输出:交叉后的种群-child_pops
'''
child_pops = []
for i in range(popsize):
flag = True
while flag:
#初始化
child = [None]*len(parent1_pops[i])
parent1 = parent1_pops[i]
parent2 = parent2_pops[i]
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
#parent1 -> child
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
# parent2 -> child
list1 = list(range(end_pos+1,len(parent2)))
list2 = list(range(0,start_pos))
list_index = list1+list2
j = -1
for i in list_index:
for j in range(j+1,len(parent2)):
if parent2[j] not in child:
child[i] = parent2[j]
break
if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#判断是否满足禁忌表
child_pops.append(child)
flag = False
return child_pops
def mutate(pops,pm,tabu_list,par_pops):
'''
#基本位变异,成对变异
输入:pops-交叉后种群,pm-变异概率,tabu_list-群居禁忌表,par_pops-上一代种群;
输出:变异后种群-pops_mutate
'''
pops_mutate = []
for i in range(len(pops)):
flag = True
while flag:
pop = pops[i].copy()
t = random.randint(1,10)#随机多次成对变异
count = 0
while count < t:
if random.random() < pm:
mut_pos1 = random.randint(0,len(pop)-1)
mut_pos2 = random.randint(0,len(pop)-1)
if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
count +=1
if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、交叉后种群禁忌,上一代种群禁忌
pops_mutate.append(pop)
flag = False
return pops_mutate
#画路径图
def draw_path(line,CityCoordinates):
x,y= [],[]
for i in line:
Coordinate = CityCoordinates[i]
x.append(Coordinate[0])
y.append(Coordinate[1])
x.append(x[0])
y.append(y[0])
plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
if __name__ == '__main__':
#参数
# CityNum = 50#城市数量
# MinCoordinate = 0#二维坐标最小值
# MaxCoordinate = 101#二维坐标最大值
#GA参数
generation = 1000 #迭代次数
popsize = 100 #种群大小
tournament_size = 5 #锦标赛小组大小
pc = 0.95 #交叉概率
pm = 0.1 #变异概率
#TS参数
tabu_limit = 100 #禁忌长度
tabu_list = [] #禁忌表
tabu_time = [] #禁忌次数
#随机生成城市数据,城市序号为0,1,2,3...
# CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
# CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
#计算城市之间的距离
dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
for i in range(len(CityCoordinates)):
xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
for j in range(len(CityCoordinates)):
xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
iteration = 0
#初始化,随机构造
pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
#计算适应度
fits = [None]*popsize
for i in range(popsize):
fits[i] = calFitness(pops[i],dis_matrix)
#保留当前最优
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
#禁忌
tabu_list.append(best_pop)
tabu_time.append(tabu_limit)
print('初代最优值 %.1f' % (best_fit))
best_fit_list = []
best_fit_list.append(best_fit)
while iteration <= generation:
#锦标赛赛选择
pop1 = tournament_select(pops,popsize,fits,tournament_size)
pop2 = tournament_select(pops,popsize,fits,tournament_size)
child_pops = crossover(popsize,pop1,pop2,pc,tabu_list,pops)#交叉
child_pops = mutate(child_pops,pm,tabu_list,pops)#变异
child_fits = [None]*popsize
for i in range(popsize):
child_fits[i] = calFitness(child_pops[i],dis_matrix)
#一对一生存者竞争
for i in range(popsize):
if fits[i] > child_fits[i]:
fits[i] = child_fits[i]
pops[i] = child_pops[i]
#更新禁忌表
tabu_time = [x-1 for x in tabu_time]
if 0 in tabu_time:
tabu_list.remove(tabu_list[tabu_time.index(0)])
tabu_time.remove(0)
if best_fit>min(fits):
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
#添加禁忌
tabu_list.append(best_pop)
tabu_time.append(tabu_limit)
best_fit_list.append(best_fit)
print('第%d代最优值 %.1f' % (iteration, best_fit))
iteration += 1
#路径顺序
print(best_pop)
# #画图
draw_path(best_pop,CityCoordinates)
#迭代图
iters = list(range(len(best_fit_list)))
plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('迭代次数')
plt.ylabel('最优解')
plt.show()
最优解为[27, 25, 23, 49, 45, 29, 43, 40, 35, 9, 31, 26, 39, 34, 14, 6, 19, 41, 20, 24, 33, 1, 0, 22, 48, 44, 13, 21, 30, 16, 37, 10, 46, 36, 18, 15, 8, 28, 42, 38, 3, 11, 47, 32, 17, 7, 2, 4, 12, 5],其适应度值为617.0,路径图和迭代效果如下。
【讨论】经过测试,多次求解质量的平均水平相比经典遗传算法有了一定的改进。
2.2 遗传-粒子群算法
2.2.1改进思路
粒子群算法的核心思想是不断向当前最优解和历史最优解的方向移动,以达到最优解,在改进遗传算法上的思路是在交叉操作上,以一定的概率(轮盘赌操作)与当前最优解或历史最优解进行交叉操作。
2.2.2 程序设计
①基于粒子群算法改进的交叉算子
def crossover(popsize,parent1_pops,pc,c1,c2,pLine,best_pop):
'''
#顺序交叉
输入:popsize-种群大小,parent1_pops-选择后父代,pc-变异系数,c1-自我认知因子,c2-社会认知因子,pLine-当前最优解,best_pop-历史最优解
输出:交叉后子代种群-child_pops
'''
child_pops = []
for i in range(popsize):
#初始化
child = [None]*len(parent1_pops[i])
parent1 = parent1_pops[i]
#求parent2
randNum = random.uniform(0, sum([c1,c2]))
if randNum <= c1:
parent2 = pLine
else:
parent2 = best_pop
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
#parent1-> child
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
# parent2 -> child
list1 = list(range(end_pos+1,len(parent2)))
list2 = list(range(0,start_pos))
list_index = list1+list2
j = -1
for i in list_index:
for j in range(j+1,len(parent2)):
if parent2[j] not in child:
child[i] = parent2[j]
break
child_pops.append(child)
return child_pops
②完整程序
# -*- coding: utf-8 -*-
"""
遗传算法求解TSP问题
随机在(0,100)二维平面生成20个点
距离最小化
"""
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 添加这条可以让图形显示中文
def calFitness(line,dis_matrix):
dis_sum = 0
dis = 0
for i in range(len(line)):
if i<len(line)-1:
dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
dis_sum = dis_sum+dis
else:
dis = dis_matrix.loc[line[i],line[0]]
dis_sum = dis_sum+dis
return round(dis_sum,1)
#锦标赛选择
def tournament_select(pops,popsize,fits,tournament_size):
new_pops = []
while len(new_pops)<len(pops):
tournament_list = random.sample(range(0,popsize),tournament_size)
tournament_fit = [fits[i] for i in tournament_list]
#转化为df方便索引
tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
#选出获胜者
pop = pops[int(tournament_df.iloc[0,0])]
new_pops.append(pop)
return new_pops
def crossover(popsize,parent1_pops,pc,c1,c2,pLine,best_pop):
'''
#顺序交叉
输入:popsize-种群大小,parent1_pops-选择后父代,pc-变异系数,c1-自我认知因子,c2-社会认知因子,pLine-当前最优解,best_pop-历史最优解
输出:交叉后子代种群-child_pops
'''
child_pops = []
for i in range(popsize):
#初始化
child = [None]*len(parent1_pops[i])
parent1 = parent1_pops[i]
#求parent2
randNum = random.uniform(0, sum([c1,c2]))
if randNum <= c1:
parent2 = pLine
else:
parent2 = best_pop
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
#parent1-> child
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
# parent2 -> child
list1 = list(range(end_pos+1,len(parent2)))
list2 = list(range(0,start_pos))
list_index = list1+list2
j = -1
for i in list_index:
for j in range(j+1,len(parent2)):
if parent2[j] not in child:
child[i] = parent2[j]
break
child_pops.append(child)
return child_pops
def mutate(pops,pm):
'''
#基本位变异,成对变异
'''
pops_mutate = []
for i in range(len(pops)):
pop = pops[i].copy()
t = random.randint(1,10)#随机多次成对变异
count = 0
while count < t:
if random.random() < pm:
mut_pos1 = random.randint(0,len(pop)-1)
mut_pos2 = random.randint(0,len(pop)-1)
if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
pops_mutate.append(pop)
count +=1
return pops_mutate
#画路径图
def draw_path(line,CityCoordinates):
x,y= [],[]
for i in line:
Coordinate = CityCoordinates[i]
x.append(Coordinate[0])
y.append(Coordinate[1])
x.append(x[0])
y.append(y[0])
plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
if __name__ == '__main__':
# #参数
# CityNum = 50#城市数量
# MinCoordinate = 0#二维坐标最小值
# MaxCoordinate = 101#二维坐标最大值
#GA参数
generation = 1000 #迭代次数
popsize = 100 #种群大小
tournament_size = 5 #锦标赛小组大小
pc = 0.95 #交叉概率
pm = 0.1 #变异概率
# n = 1#灾变系数
#PSO参数
c1 = 0.5#自我认知因子
c2 = 0.5#社会认知因子
pBest,pLine =0,[]#当前最优值、当前最优解,(自我认知部分)
#随机生成城市数据,城市序号为0,1,2,3...
# CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
# CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
#计算城市之间的距离
dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
for i in range(len(CityCoordinates)):
xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
for j in range(len(CityCoordinates)):
xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
iteration = 0
#初始化,随机构造
pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
#计算适应度
fits = [None]*popsize
for i in range(popsize):
fits[i] = calFitness(pops[i],dis_matrix)
#保留当前最优
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
pBest,pLine = min(fits),pops[fits.index(best_fit)]
print('初代最优值 %.1f' % (best_fit))
best_fit_list = []
best_fit_list.append(best_fit)
while iteration <= generation:
pop1 = tournament_select(pops,popsize,fits,tournament_size)#锦标赛赛选择
child_pops = crossover(popsize,pop1,pc,c1,c2,pLine,best_pop)#交叉
child_pops = mutate(child_pops,pm)#变异
child_fits = [None]*popsize
for i in range(popsize):
child_fits[i] = calFitness(child_pops[i],dis_matrix)
#一对一生存者竞争
for i in range(popsize):
if fits[i] > child_fits[i]:
fits[i] = child_fits[i]
pops[i] = child_pops[i]
if best_fit>min(fits):
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
pBest,pLine = min(fits),pops[fits.index(best_fit)]
# n = 1#灾变系数
elif pBest>min(fits):
pBest,pLine = min(fits),pops[fits.index(best_fit)]
# n += 1
# else:
# n += 1
# if n == 20:#20代历史最优值没变化就发生灾变
# for i in range(int(len(pops)/2)):
# pops[i] = random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates))
best_fit_list.append(best_fit)
print('第%d代最优值 %.1f' % (iteration, best_fit))
iteration += 1
#路径顺序
print(best_pop)
# #画图
draw_path(best_pop,CityCoordinates)
#迭代图
iters = list(range(len(best_fit_list)))
plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('迭代次数')
plt.ylabel('最优解')
plt.show()
最优解为[42, 11, 28, 8, 15, 18, 36, 46, 48, 0, 1, 22, 44, 10, 33, 13, 16, 30, 37, 21, 24, 20, 40, 35, 45, 9, 41, 19, 6, 34, 14, 39, 7, 4, 2, 12, 5, 27, 25, 23, 49, 43, 29, 31, 26, 17, 32, 47, 3, 38],其适应度值为732.6,路径图和迭代效果如下。
【讨论】粒子群算法改进遗传算法的表现与经典遗传算法相比,表现更差了,深入分析后也可以理解这一结果:在我设计的算法中,只按一定的概率与当前最优解或者历史最优解进行交叉,一定程度上使得种群极易陷入局部解,且缺乏多样性的种群很难跳出局部最优解。尝试引入“灾变”技术进行改进(代码中有保留,灾变系数n注释掉的部分),但是效果也不佳,归根到底,种群多样性少会使得种群搜索效率变得较低。
2.3 遗传-禁忌搜索-粒子群算法
2.3.1改进思路
粒子群算法改进的效果不佳,一定部分原因是收敛太快导致使得搜索效率低下,为了提高搜索效率,这里再引入禁忌搜索算法的思想尝试进行改进,改进思路是前面两种算法的结合,即在交叉算子和变异算子中引入禁忌搜索的思想,在交叉算子中叠加粒子群算法的思想。
2.3.2 程序设计
# -*- coding: utf-8 -*-
"""
遗传算法求解TSP问题
随机在(0,100)二维平面生成50个点
距离最小化
"""
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 添加这条可以让图形显示中文
def calFitness(line,dis_matrix):
dis_sum = 0
dis = 0
for i in range(len(line)-1):
dis = dis_matrix.loc[line[i],line[i+1]]#计算距离
dis_sum = dis_sum+dis
dis = dis_matrix.loc[line[-1],line[0]]
dis_sum = dis_sum+dis
return round(dis_sum,1)
#锦标赛选择
def tournament_select(pops,popsize,fits,tournament_size):
new_pops = []
while len(new_pops)<len(pops):
tournament_list = random.sample(range(0,popsize),tournament_size)
tournament_fit = [fits[i] for i in tournament_list]
#转化为df方便索引
tournament_df = pd.DataFrame([tournament_list,tournament_fit]).transpose().sort_values(by=1).reset_index(drop=True)
#选出获胜者
pop = pops[int(tournament_df.iloc[0,0])]
new_pops.append(pop)
return new_pops
#顺序交叉
def crossover(popsize,parent1_pops,pc,tabu_list,par_pops,c1,c2,pLine,best_pop):
child_pops = []
for i in range(popsize):
flag = True
while flag:
#初始化
child = [None]*len(parent1_pops[i])
parent1 = parent1_pops[i]
#求parent2
randNum = random.uniform(0, sum([c1,c2]))
if randNum <= c1:
parent2 = pLine
else:
parent2 = best_pop
if random.random() >= pc:
child = parent1.copy()#随机生成一个
random.shuffle(child)
else:
#parent1 -> child
start_pos = random.randint(0,len(parent1)-1)
end_pos = random.randint(0,len(parent1)-1)
if start_pos>end_pos:start_pos,end_pos = end_pos,start_pos
child[start_pos:end_pos+1] = parent1[start_pos:end_pos+1].copy()
# parent2 -> child
list1 = list(range(end_pos+1,len(parent2)))
list2 = list(range(0,start_pos))
list_index = list1+list2
j = -1
for i in list_index:
for j in range(j+1,len(parent2)):
if parent2[j] not in child:
child[i] = parent2[j]
break
if (child not in tabu_list) & (child not in child_pops) & (child not in par_pops):#全局禁忌、当前禁忌、上一代种群禁忌
child_pops.append(child)
flag = False
return child_pops
#基本位变异,成对变异
def mutate(pops,pm,tabu_list,par_pops):
pops_mutate = []
for i in range(len(pops)):
flag = True
while flag:
pop = pops[i].copy()
#随机多次成对变异
t = random.randint(1,10)
count = 0
while count < t:
if random.random() < pm:
mut_pos1 = random.randint(0,len(pop)-1)
mut_pos2 = random.randint(0,len(pop)-1)
if mut_pos1 != mut_pos2:pop[mut_pos1],pop[mut_pos2] = pop[mut_pos2],pop[mut_pos1]
count +=1
if (pop not in tabu_list) & (pop not in pops_mutate) & (pop not in pops) & (pop not in par_pops):#全局禁忌、当前禁忌、变异之前的群体禁忌
pops_mutate.append(pop)
flag = False
return pops_mutate
#画路径图
def draw_path(line,CityCoordinates):
x,y= [],[]
for i in line:
Coordinate = CityCoordinates[i]
x.append(Coordinate[0])
y.append(Coordinate[1])
x.append(x[0])
y.append(y[0])
plt.plot(x, y,'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
if __name__ == '__main__':
#参数
CityNum = 50#城市数量
MinCoordinate = 0#二维坐标最小值
MaxCoordinate = 101#二维坐标最大值
#GA参数
generation = 1000 #迭代次数
popsize = 100 #种群大小
tournament_size = 5 #锦标赛小组大小
pc = 0.95 #交叉概率
pm = 0.1 #变异概率
#TS参数
tabu_limit = 200 #禁忌长度
tabu_list = [] #禁忌表
tabu_time = [] #禁忌次数
#PSO参数
c1 = 0.5#自我认知因子
c2 = 0.5#社会认知因子
pBest,pLine =0,[]#当前最优值、当前最优解,(自我认知部分)
#随机生成城市数据,城市序号为0,1,2,3...
# CityCoordinates = [(random.randint(MinCoordinate,MaxCoordinate+1),random.randint(MinCoordinate,MaxCoordinate+1)) for i in range(CityNum)]
# CityCoordinates = [(88, 16),(42, 76),(5, 76),(69, 13),(73, 56),(100, 100),(22, 92),(48, 74),(73, 46),(39, 1),(51, 75),(92, 2),(101, 44),(55, 26),(71, 27),(42, 81),(51, 91),(89, 54),(33, 18),(40, 78)]
CityCoordinates = [(71, 71),(68, 71),(19, 41),(9, 67),(22, 34),(15, 2),(60, 56),(36, 38),(18, 92), (96, 27),(71, 85),(24, 70),(12, 31),(77, 88),(59, 49),(27, 87),(94, 97),(37, 42),(32, 78),(65, 57), (96, 47),(95, 86),(61, 80),(55, 7),(94, 74),(39, 6),(62, 43),(34, 11),(18, 89),(79, 16),(100, 99),(76, 39),(35, 51),(74, 71),(59, 48),(98, 1),(35, 98),(82, 91),(0, 64),(56, 48),(89, 8),(69, 54),(3, 72),(79, 16),(66, 88),(80, 15),(56, 88),(30, 57),(67, 86),(75, 4)]
#计算城市之间的距离
dis_matrix = pd.DataFrame(data=None,columns=range(len(CityCoordinates)),index=range(len(CityCoordinates)))
for i in range(len(CityCoordinates)):
xi,yi = CityCoordinates[i][0],CityCoordinates[i][1]
for j in range(len(CityCoordinates)):
xj,yj = CityCoordinates[j][0],CityCoordinates[j][1]
dis_matrix.iloc[i,j] = round(math.sqrt((xi-xj)**2+(yi-yj)**2),2)
iteration = 0
#初始化,随机构造
pops = [random.sample([i for i in list(range(len(CityCoordinates)))],len(CityCoordinates)) for j in range(popsize)]
#计算适应度
fits = [None]*popsize
for i in range(popsize):
fits[i] = calFitness(pops[i],dis_matrix)
#保留当前最优
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
pBest,pLine = min(fits),pops[fits.index(best_fit)]
#禁忌
tabu_list.append(best_pop)
tabu_time.append(tabu_limit)
print('初代最优值 %.1f' % (best_fit))
best_fit_list = []
best_fit_list.append(best_fit)
while iteration <= generation:
pop1 = tournament_select(pops,popsize,fits,tournament_size)#锦标赛赛选择
child_pops = crossover(popsize,pop1,pc,tabu_list,pops,c1,c2,pLine,best_pop)#交叉
child_pops = mutate(child_pops,pm,tabu_list,pops)#变异
child_fits = [None]*popsize
for i in range(popsize):
child_fits[i] = calFitness(child_pops[i],dis_matrix)
# #一对一生存者竞争
for i in range(popsize):
if fits[i] > child_fits[i]:
fits[i] = child_fits[i]
pops[i] = child_pops[i]
#更新禁忌表
tabu_time = [x-1 for x in tabu_time]
if 0 in tabu_time:
tabu_list.remove(tabu_list[tabu_time.index(0)])
tabu_time.remove(0)
if best_fit>min(fits):
best_fit = min(fits)
best_pop = pops[fits.index(best_fit)]
pBest,pLine = min(fits),pops[fits.index(best_fit)]
elif pBest>min(fits):
pBest,pLine = min(fits),pops[fits.index(best_fit)]
#添加禁忌
tabu_list.append(best_pop)
tabu_time.append(tabu_limit)
best_fit_list.append(best_fit)
print('第%d代最优值 %.1f' % (iteration, best_fit))
iteration += 1
#路径顺序
print(best_pop)
# #画图
draw_path(best_pop,CityCoordinates)
#迭代图
iters = list(range(len(best_fit_list)))
plt.plot(iters, best_fit_list, 'r-', color='#4169E1', alpha=0.8, linewidth=0.8)
plt.xlabel('迭代次数')
plt.ylabel('最优解')
plt.show()
最优解为[30, 16, 37, 13, 46, 36, 8, 28, 15, 18, 11, 42, 38, 3, 47, 32, 2, 4, 12, 5, 27, 25, 23, 7, 17, 14, 39, 19, 41, 6, 34, 26, 31, 43, 45, 29, 49, 40, 35, 9, 20, 0, 10, 48, 44, 22, 1, 33, 24, 21],其适应度值为663.0,路径图和迭代图如下:
【讨论】在粒子群改进的基础上加入禁忌搜索算法思想,整体效果比遗传-粒子群算法和经典遗传算法效果稍微稳定一些,但稍微逊色于遗传-禁忌搜索算法。
3. 总结
本文分别尝试了使用禁忌搜索算法、粒子群算法对遗传算法进行改进,并尝试结合三种算法的思想构建遗传-禁忌搜索-粒子群算法,在以上三种改进中,遗传-禁忌搜索算法的求解效果是比较好的,当然这也与算法设计有很大的关系,不同的算子设计可能带来不同的效果,而粒子群算法改进遗传算法的效果适得其反,对比单纯用【粒子群算法求解TSP问题】的设计,在遗传算法上可能是选择操作使得进行交叉的种群多样性较少(粒子群和遗传算法都有交叉操作),进而降低了对解空间的搜索效率。
学习完TSP问题求解系列,最大的感受就是初始解的构造上对求解效率的影响非常大,良好的初始解可以减少大量冗余的遍历过程,且极易获得较优解。但是,也存在某些问题上可能很难设计出较好的初始解(比如多约束的VRP问题),这种情况下问题的求解对算法依赖比较大,需要选中更加合适的算法或者结合多种算法思想进行求解。
TSP系列目录
智能优化算法类别 | 启发式算法求解TSP问题系列博文 |
---|---|
进化算法 | 遗传算法求解TSP问题 |
仿人智能优化算法 | 禁忌搜索算法求解TSP问题 |
仿自然优化算法 | 模拟退火算法求解TSP问题 |
群智能优化算法 | 蚁群算法求解TSP问题 |
群智能优化算法 | 粒子群算法求解TSP问题 |
总结篇 | 五种常见启发式算法求解TSP问题 |
改进篇 | 遗传-粒子群算法&遗传-禁忌搜索算法求解TSP问题 |
记录学习过程,欢迎指正