一、基本步骤
遗传算法求解旅行商问题(TSP)的一般步骤如下:
-
编码:
- 通常采用整数编码,将城市的访问顺序表示为一个染色体。例如,假设有 5 个城市,编码为[1, 3, 5, 2, 4],表示旅行商的访问顺序为城市 1、城市 3、城市 5、城市 2、城市 4,最后回到出发城市 1。
-
初始化种群:
- 随机生成一定数量的染色体,作为初始种群。
-
适应度评估:
- 计算每个染色体所代表的路径长度,路径长度越短,适应度越高。适应度函数可以是路径长度的倒数或其他与路径长度相关的函数。
-
选择操作:
- 基于适应度,选择一定比例的染色体进入下一代种群。常见的选择方法有轮盘赌选择、锦标赛选择等。
-
交叉操作:
- 对选中的染色体进行交叉,生成新的染色体。例如,单点交叉,随机选择一个交叉点,交换两个染色体在交叉点之后的部分。
-
变异操作:
- 以一定的概率对染色体中的基因进行变异,例如随机交换两个城市的位置。
-
重复步骤 3 - 6 ,直到满足终止条件(如达到预定的迭代次数或找到满意的解)。
通过不断迭代,种群中的染色体逐渐优化,最终得到较优的 TSP 路径。需要注意的是,遗传算法的参数(如种群大小、交叉概率、变异概率等)需要根据具体问题进行调整和优化,以获得更好的求解效果。
二、代码
#!/usr/bin/env python
# coding: utf-8
# In[32]:
import numpy as np
import copy
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.pyplot import MultipleLocator
import random
# In[33]:
#准备好距离矩阵
city_num = 5
city_dist_mat = np.zeros([city_num, city_num])
city_dist_mat[0][1] = city_dist_mat[1][0] = 1165
city_dist_mat[0][2] = city_dist_mat[2][0] = 1462
city_dist_mat[0][3] = city_dist_mat[3][0] = 3179
city_dist_mat[0][4] = city_dist_mat[4][0] = 1967
city_dist_mat[1][2] = city_dist_mat[2][1] = 1511
city_dist_mat[1][3] = city_dist_mat[3][1] = 1942
city_dist_mat[1][4] = city_dist_mat[4][1] = 2129
city_dist_mat[2][3] = city_dist_mat[3][2] = 2677
city_dist_mat[2][4] = city_dist_mat[4][2] = 1181
city_dist_mat[3][4] = city_dist_mat[4][3] = 2216
#标号说明
#list_city = ['0_北京', '1_西安', '2_上海', '3_昆明', '4_广州']
# In[34]:
#1.定义个体类,包括基因(城市路线)和适应度
num_person_idx = 0
num_person = 0
dis_list = []
class Individual:
def __init__(self, genes = None):
global num_person
global dis_list
global num_person_idx
num_person_idx += 1
if num_person_idx % 20 == 0:
num_person += 1
self.genes = genes
if self.genes == None:
genes = [0]*5
temp = [0]*4
temp = [i for i in range(1,city_num)]########################################################################
random.shuffle(temp)
genes[1:] = temp
genes[0] = 0
self.genes = genes
self.fitness = self.evaluate_fitness()
else:
self.fitness = float(self.evaluate_fitness())
#2. #计算个体的适应度
def evaluate_fitness(self):
dis = 0
for i in range(city_num - 1):
dis += city_dist_mat[self.genes[i]][self.genes[i+1]]
if i == city_num - 2:
dis += city_dist_mat[self.genes[i + 1]][0]#回到0
if num_person_idx % 20 == 0:
dis_list.append(dis)
return 1/dis
# In[35]:
def copy_list(old):
new = []
for element in old:
new.append(element)
return new
def sort_win_num(group):
for i in range(len(group)):
for j in range(len(group) - i - 1):
# print('group[j].fitness_type', type(group[j].fitness))
if group[j].fitness < group[j+1].fitness:
temp = group[j]
group[j] = group[j+1]
group[j+1] = temp
return group
#定义Ga类
#3~5,交叉、变异、更新种群,全部在Ga类中实现
class Ga:
#input_为城市间的距离矩阵
def __init__(self, input_):
#声明一个全局变量
global city_dist_mat
city_dist_mat = input_
#当代的最佳个体########################################此处做了更改
#self.best = None
self.best = Individual(None)
# print("BBBBBBbest.fitness",self.best.fitness)
#种群
self.individual_list = []
#每一代的最佳个体
self.result_list = []
#每一代个体对应的最佳适应度
self.fitness_list = []
#交叉,这里采用交叉变异
def cross(self):
new_gen = []
#随机选取一段,含有num_cross个数字(城市)
num_cross = 3#后期可能需要调试的参数,考虑到实际问题里只有5个城市,所以认为3较为合适
for i in range(0, len(self.individual_list) - 1, 2):
parent_gen1 = copy_list(self.individual_list[i].genes)
parent_gen2 = copy_list(self.individual_list[i+1].genes)
# print("parent_gen1",parent_gen1)
# print("parent_gen2",parent_gen2)
index1_1 = 0
index1_2 = 0
index2_1 = 0
index2_2 = 0
#定义一个下表列表
index_list = [0]*3
for i in range(city_num - 3):#就是2,即0,1
index_list[i] = i + 1
index1_1 = random.choice(index_list)
index1_2 = index1_1 + 2
index2_1 = random.choice(index_list)
index2_2 = index2_1 + 2
choice_list1 = parent_gen1[index1_1:index1_2 + 1]
choice_list2 = parent_gen2[index2_1:index2_2 + 1]
# print("choice_list1",choice_list1)
# print("choice_list2",choice_list2)
#利用这一段生成两个子代,下面的赋值只是为了获取长度,所以用哪个父代能可以
#也可以直接用city_num直接代替
son_gen1 = [0]*city_num
son_gen2 = [0]*city_num
# print('son_gen1_size = ',len(son_gen1))
# print('son_gen2_size = ',len(son_gen2))
# print("index1_1 == ",index1_1)
# print("index1_2 == ",index1_2)
# print("index2_1 == ",index2_1)
# print("index2_2 == ",index2_2)
#找到之后进行交叉,分别得到son_gen1,son_gen2
#先把选中的段复制进去
son_gen1[index1_1: index1_2 + 1] = choice_list1
son_gen2[index2_1: index2_2 + 1] = choice_list2
# print("now, son_gen1 = ", son_gen1)
# print("now, son_gen2 = ", son_gen2)
#然后左、右“查漏补缺”
temp1 = choice_list1
temp2 = choice_list2
if index1_1 == 0:
pass
else:
for i in range(index1_1):
for j in range(city_num):
#如果父代2里面的这个当初没被选中,那就加入son_gene1
if parent_gen2[j] not in choice_list1:
son_gen1[i] = parent_gen2[j]
#这个时候要扩增choice_list1, 这样parent_gen2里面未被选中的元素才会一个个被遍历到#1
choice_list1.append(parent_gen2[j])
#找到之后马上break,防止被覆盖
break
choice_list1 = temp1
if index1_2 == city_num - 1:
pass
else:
for i in range(index1_2 + 1, city_num):
for j in range(city_num):
if parent_gen2[j] not in choice_list1:
son_gen1[i] = parent_gen2[j]
#这个时候要扩增choice_list1, 这样parent_gen2里面未被选中的元素才会一个个被遍历到#2
choice_list1.append(parent_gen2[j])
#找到之后马上break,防止被覆盖
break
#son_gen2亦是如此
if index2_1 == 0:
pass
else:
for i in range(index2_1):
for j in range(city_num):
#如果父代1里面的这个当初没被选中,那就加入son_gen2
if parent_gen1[j] not in choice_list2:
son_gen2[i] = parent_gen1[j]
#这个时候要扩增choice_list2, 这样parent_gen1里面未被选中的元素才会一个个被遍历到#3
choice_list2.append(parent_gen1[j])
#找到之后马上break,防止被覆盖
break
choice_list2 = temp2
if index2_2 == city_num - 1:
pass
else:
for i in range(index2_2 + 1, city_num):
for j in range(city_num):
if parent_gen1[j] not in choice_list2:
# print("i == ", i)
son_gen2[i] = parent_gen1[j]
#这个时候要扩增choice_list2, 这样parent_gen1里面未被选中的元素才会一个个被遍历到#4
choice_list2.append(parent_gen1[j])
#找到之后马上break,防止被覆盖
break
#新生成的子代基因加入new_gene列表
# print('son_gen1 = ',son_gen1)
# print('son_gen2 = ',son_gen2)
new_gen.append(Individual(son_gen1))
#print('new_gen[-1].genes', new_gen[-1].genes)
new_gen.append(Individual(son_gen2))
return new_gen
#变异
def mutate(self, new_gen):
mutate_p = 0.02#待调参数
index_list = [0]*(city_num-1)
index_1 = 1
index_2 = 1
for i in range(city_num - 1):
index_list[i] = i + 1
for individual in new_gen:
if random.random() < mutate_p:
# change += 1
#如果变异,采用基于位置的变异,方便起见,直接使用上面定义的index列表
index_l = random.choice(index_list)
# index_2 = (index_1 + 2) % city_num#这里让间隔为2的两个城市进行交换
index_2 = random.choice(index_list)
while index_1 == index_2:
index_2 = random.choice(index_list)
#交换
temp = individual.genes[index_1]
individual.genes[index_1] = individual.genes[index_2]
individual.genes[index_2] = temp
#变异结束,与老一代的进行合并
self.individual_list += new_gen
#选择
def select(self):
#在此选用轮盘赌算法
#考虑到5的阶乘是120,所以可供选择的个体基数应该适当大一些,
#在此每次从种群中选择6个,进行轮盘赌,初始化60个个体,同时适当调高变异的概率
select_num = 6
select_list = []
for i in range(select_num):
gambler = random.choice(self.individual_list)
gambler = Individual(gambler.genes)
select_list.append(gambler)
#求出这些fitness之和
sum = 0
for i in range(select_num):
sum += select_list[i].fitness
sum_m = [0]*select_num
#实现概率累加
for i in range(select_num):
for j in range(i+1):
sum_m[i] += select_list[j].fitness
sum_m[i] /= sum
new_select_list = []
p_num = 0#随机数
for i in range(select_num):
p_num = random.uniform(0,1)
if p_num>0 and p_num < sum_m[0]:
new_select_list.append(select_list[0])
elif p_num>= sum_m[0] and p_num < sum_m[1]:
new_select_list.append(select_list[1])
elif p_num >= sum_m[1] and p_num < sum_m[2]:
new_select_list.append(select_list[2])
elif p_num >= sum_m[2] and p_num < sum_m[3]:
new_select_list.append(select_list[3])
elif p_num >= sum_m[3] and p_num < sum_m[4]:
new_select_list.append(select_list[4])
elif p_num >= sum_m[4] and p_num < sum_m[5]:
new_select_list.append(select_list[5])
else:
pass
#将新生成的一代替代父代种群
self.individual_list = new_select_list
#更新种群
def next_gen(self):
#交叉
new_gene = self.cross()
#变异
self.mutate(new_gene)
#选择
self.select()
#获得这一代的最佳个体
# print("**************************************")
# print('self.best.fitness = ', self.best.fitness)
# print('now, best.fitness = ', self.best.fitness)
for individual in self.individual_list:
if individual.fitness > self.best.fitness:
self.best = individual
# print("更换了最优路径")
# print('now, best.fitness = ', self.best.fitness)
def train(self):
#随机出初代种群#
individual_num = 60
self.individual_list = [Individual() for _ in range(individual_num)]
#迭代
gen_num = 100
for i in range(gen_num):
#从当代种群中交叉、变异、选择出适应度最佳的个体,获得子代产生新的种群
self.next_gen()
#连接首位
# print("i = ", i)
result = copy.deepcopy(self.best.genes)
result.append(result[0])
self.result_list.append(result)
self.fitness_list.append(self.best.fitness)
print(self.result_list[-1])
print('距离总和是:', 1/self.fitness_list[-1])
# return self.result_list, self.fitness_list
def draw(self):
x_list = [i for i in range(num_person)]
y_list = dis_list
plt.rcParams['figure.figsize'] = (60, 45)
plt.plot(x_list, y_list, color = 'g')
plt.xlabel('Cycles',size = 50)
plt.ylabel('Route',size = 50)
x = np.arange(0, 910, 20)
y = np.arange(7800, 12000, 100)
plt.xticks(x)
plt.yticks(y)
plt.title('Trends in distance changes', size = 50)
plt.tick_params(labelsize=30)
# plt.savefig("D:\AI_pictures\遗传算法求解TSP问题_1_轮盘赌算法")
plt.show()
route = Ga(city_dist_mat)
route.train()
route.draw()