TSP问题 (Travelling Salesman Problem): 一个商品推销员要去若干个城市推销商 品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择 行进路线,以使总的行程最短。使用遗传算法求解。
完整代码地址[email protected]:swn233/Genetic-algorithm-to-solve-the-traveling-salesman-problem.git
实验数据集eil51可在github上搜索下载
本文采用的朴素遗传算法,步骤包括:
1.根据population_size初始化种群,其中的每一个个体代表一个TSP问题的可行解,即各个城市编号出现一次
2.计算两两城市之间距离,存入distance矩阵,用于计算个体适应度
3.计算种群中每个个体的适应度,数值为个体代表行路方案的总距离倒数
4.利用轮盘赌产生新的种群
5.进行基于位置的交叉重组,重组概率表示种群中发生重组的个体数量,num_node表示重组的基因数量
6.进行变异,选取任意一个个体,随机交换两个基因的位置
7.计算种群平均适应度,方便绘图
8.迭代计算
population_size=20
Loop_time=1000
c_rate=0.2,num_node=3
m_rate=0.1
种群变化率过大,算法无法收敛,尝试增大种群数量,减小重组率和变异率
population_size=200
Loop_time=1000
c_rate=0.02,num_node=3
m_rate=0.01
算法能够朝着最大化适应度的方向前进,但仍未收敛,尝试增大迭代次数
population_size=200
Loop_time=20000
c_rate=0.02,num_node=3
m_rate=0.01
继续增大迭代次数,当然也可以增大重组概率和变异概率,使得算法更快收敛
通过多次调参算法最后收敛
最终求得的最短路径545.1417783540622
方案如下
删除额外信息后的结构化数据:
首先读取数据集:
file_path='../eil51-tsp.txt'
cities = []
with open(file_path, 'r') as file:
for line in file:
city_info = line.strip().split()
city_info = [int(info) for info in city_info]
cities.append(city_info)
绘制图像代码:
# 提取坐标数据
x = [city[1] for city in cities]
y = [city[2] for city in cities]
# 绘制散点图
plt.scatter(x, y)
# 添加城市编号标签
for city in cities:
plt.annotate(city[0], (city[1], city[2]), textcoords="offset points", xytext=(0,10), ha='center')
# 设置图形标题和坐标轴标签
plt.title("City Visualization")
plt.xlabel("X-coordinate")
plt.ylabel("Y-coordinate")
# 显示图形
plt.show()
种群初始化函数,构造一个指定种群大小的初始化种群,其中的每个个体都是一个TSP问题的可行解
#种群初始化
import random
import math
import copy
from pprint import pprint
#传入种群大小,初始化种群
def init_population(population_size):
population=[]
#二维列表,每一行为一个染色体,即路径方案
for i in range(population_size):
#单个染色体
chromosome=[]
while chromosome.__len__()!=51:
#随机选取一个城市,需要是未被选取过的
rand=random.randint(0,50)
if rand not in chromosome:
chromosome.append(rand)
else:
continue
population.append(copy.deepcopy(chromosome))
#print(population)
#chromosome.sort()
#print(chromosome)
chromosome.clear()
return population
#初始化种群
#population_size=20
#population=init_population(population_size)
# print(population)
计算任意两城市之间的距离,得到距离矩阵,用于求解适应度
#任意两城市间距离distance作为代价,距离之和的倒数即为适应度
def distance_func(cities):
distance=[[0 for x in range(51)] for y in range(51)]
x = [city[1] for city in cities]
y = [city[2] for city in cities]
for i in range(51):
for j in range(51):
distance[i][j]=math.sqrt((x[i]-x[j])**2+(y[i]-y[j])**2)
return distance
distance=distance_func(cities)
适应度函数,适应度为单个路线方案路径总和的倒数,由于目标为总路径最小,所以适应度越大越好
#个体适应度计算,将最小化目标函数转换为最大化目标函数,即对总距离取倒数
def fitness_func(choromosome):
fitness=0
for i in range(51):
if i!=50:
start=choromosome[i]
end=choromosome[i+1]
fitness+=distance[start][end]
else :
start=choromosome[i]
end=choromosome[0]
fitness+=distance[start][end]
return 1000/fitness
# fitness=fitness_func(population[0])
# fitness
#计算所有个体的适应度
def fitness_all(population):
fitness=[]
for i in range(population_size):
tmp=fitness_func(population[i])
fitness.append(copy.deepcopy(tmp))
return fitness
平均适应度计算,用于迭代过程中绘图:
#计算平均适应度,用于绘图
def fitness_average():
#计算所有个体的适应度
fitness=[]
for i in range(population_size):
tmp=fitness_func(population[i])
fitness.append(copy.deepcopy(tmp))
fitness_aver=sum(fitness)/len(fitness)
return fitness_aver
fitness_aver=fitness_average()
fitness_aver
选择函数,采用轮盘赌方法产生新的种群:
#采用轮盘赌方法产生新的种群
def select_func(fitness):
#计算各个个体被选中的累计概率
new_population=[]
fit_sum=sum(fitness)
possibility=[value/fit_sum for value in fitness]
possibility=np.cumsum(possibility)
for i in range(population_size):
#产生一个0到1之间的随机数,依据随机数出现在上述哪个概率区域内来确定各个个体被选中的次数,将被选中的个体加入新的种群中
rand=random.random()
for j in range(population_size):
if rand>possibility[j]:
continue
else:
new_population.append(copy.deepcopy(population[j]))
break
return new_population
new_population=select_func(fitness)
new_population
重组函数,采用基于位置的交叉:
#交叉重组
#c_rate交叉概率
#num_node交叉基因数
#采用基于位置的交叉,在样本1中随机选择交换基因,根据交叉部分生成新的样本1,再将样本2中未被选择到的基因按顺序复制到样本1中
def crossover(population, c_rate, num_node):
cross_time = math.floor(len(population) * c_rate)
for i in range(cross_time):
# 在种群中随机选取两个个体
random_chromosome1_index = random.randint(0, len(population) - 1)
random_chromosome2_index = random.randint(0, len(population) - 1)
# print(random_chromosome1_index, random_chromosome2_index)
# 确保两个个体不同
while random_chromosome1_index == random_chromosome2_index:
random_chromosome2_index = random.randint(0, len(population) - 1)
random_chromosome1 = copy.deepcopy(population[random_chromosome1_index])
random_chromosome2 = copy.deepcopy(population[random_chromosome2_index])
random_chromosome1_backup = copy.deepcopy(random_chromosome1)
random_chromosome2_backup = copy.deepcopy(random_chromosome2)
# print(random_chromosome1_backup,random_chromosome2_backup)
# 随机选取num_node个基因进行重组
index_gene = random.sample(range(len(random_chromosome1)), num_node)
# print(index_gene)
# 样本一中选择保留的基因,已经存在的基因
retain1=[]
for gene in index_gene:
random_chromosome1[gene] = random_chromosome1_backup[gene]
retain1.append(random_chromosome1[gene])
# print(retain1)
#样本二中与样本一中保留基因不同的按顺序复制
for gene1 in range(len(random_chromosome1)):
if gene1 not in index_gene:
for gene2 in range(len(random_chromosome1)):
if random_chromosome2_backup[gene2] not in retain1:
random_chromosome1[gene1] = random_chromosome2_backup[gene2]
# print("fuzhi",random_chromosome1)
retain1.append(random_chromosome1[gene1])
break
else:
continue
# print(retain1)
# print(random_chromosome1)
# 对样本2进行同样的操作
retain2=[]
for gene in index_gene:
random_chromosome2[gene] = random_chromosome2_backup[gene]
retain2.append(random_chromosome2[gene])
# print(retain2)
for gene1 in range(len(random_chromosome1)):
if gene1 not in index_gene:
for gene2 in range(len(random_chromosome1)):
if random_chromosome1_backup[gene2] not in retain2:
random_chromosome2[gene1] = random_chromosome1_backup[gene2]
# print("fuzhi",random_chromosome2)
retain2.append(random_chromosome2[gene1])
break
else:
continue
# print(retain2)
# print(random_chromosome2)
# print("***",random_chromosome1,"***",random_chromosome2)
# 更新种群中的个体
population[random_chromosome1_index] = random_chromosome1
population[random_chromosome2_index] = random_chromosome2
return population
# 示例用法
# population_size = 4
# population = [[0, 1, 2, 3, 4, 5], [5,4,3,2,1,0], [2,3,1,5,4,0], [3,5,1,2,4,0]]
# crossover(population, 0.8, 3)
# print(population)
变异函数,随机交换一条染色体上的两个基因:
#变异m_rate:变异率,m_rate*population即需要变异的染色体数量
#每次在个体基因序列中选择两个基因的位置进行交换
def mutation(population, m_rate):
mutation_time = math.floor(len(population) * m_rate)
# print(mutation_time)
# 随机抽取染色体
chromosome_index = random.sample(range(len(population)), mutation_time)
# print(chromosome_index)
for index in chromosome_index:
gene1 = random.randint(0, len(population[0]) - 1)
gene2 = random.randint(0, len(population[0]) - 1)
# print(gene1,gene2)
tmp = population[index][gene1]
population[index][gene1] = population[index][gene2]
population[index][gene2] = tmp
return population
# 示例用法
# population = [[0, 1, 2, 3, 4, 5], [5, 4, 3, 2, 1, 0], [2, 3, 1, 5, 4, 0], [3, 5, 1, 2, 4, 0]]
# m_rate = 0.25
# mutated_population = mutation(population, m_rate)
# print(mutated_population)
调用函数进行迭代:
#调用函数进行遗传算法,初始化种群在函数定义块中运行一次,distance矩阵计算一次,剩余的选择,重组,变异需要循环迭代多次
#遗传算法迭代次数
population_size=200
population=init_population(population_size)
Loop_time=1000
fitness_aver_list=[]
for i in range(Loop_time):
fitness=fitness_all(population)
population=select_func(fitness)
population=crossover(population,c_rate=0.02,num_node=3)
population=mutation(population,m_rate=0.01)
#计算本次的平均适应度函数并描点
fitness_aver=fitness_average()
print("第",i,"次迭代:",fitness_aver)
fitness_aver_list.append(fitness_aver) # 将平均适应度添加到列表中
# 一次性绘制数据曲线
plt.plot(range(Loop_time), fitness_aver_list, 'b-')
plt.xlabel('Iteration')
plt.ylabel('Fitness Average')
plt.title('Fitness Average over Iterations')
plt.show()
计算最终种群中每个个体的适应度,从中挑选出最优个体
#计算最终种群中每个个体的适应度,从中挑选出最优个体
fitness=fitness_all(population)
best_index = np.argmax(fitness)
min_distance=1000/fitness[best_index]
best_individual = population[best_index]
print(min_distance)
best_individual
绘图显示方案
x = [city[1] for city in cities]
y = [city[2] for city in cities]
# 绘制散点图
plt.scatter(x, y)
# 添加城市编号标签
for city in cities:
plt.annotate(city[0], (city[1], city[2]), textcoords="offset points", xytext=(0,10), ha='center')
# 绘制连线
for i in range(len(best_individual)-1):
city1 = best_individual[i]
city2 = best_individual[i+1]
x1, y1 = cities[city1][1], cities[city1][2]
x2, y2 = cities[city2][1], cities[city2][2]
plt.plot([x1, x2], [y1, y2], 'r-')
# 连接首尾城市
city1 = best_individual[-1]
city2 = best_individual[0]
x1, y1 = cities[city1][1], cities[city1][2]
x2, y2 = cities[city2][1], cities[city2][2]
plt.plot([x1, x2], [y1, y2], 'r-')
# 设置图形标题和坐标轴标签
plt.title("City Visualization")
plt.xlabel("X-coordinate")
plt.ylabel("Y-coordinate")
# 显示图形
plt.show()
参考文章:
http://t.csdnimg.cn/afMsY
http://t.csdnimg.cn/8HAnC