該代碼是本人根據B站up主侯昶曦的代碼所修改的。
原代碼github地址:https://github.com/Houchangxi/heuristic-algorithm/blob/master/TSP問題遺傳算法/Genetic Algorithm.py
遺傳算法步驟不用講了,將再多還是不會寫代碼,倒不如花一上午讀懂下面的代碼。不僅明白了具體步驟還能寫出代碼。

#!/usr/bin/env python
# coding: utf-8
# Author:侯昶曦 & 孟慶國
# Date:2020年5月19日 21點16分
# * 本代碼中使用的城市坐標需要保存在一個`csv`類型的表中。
# * 下面的代碼可以生成隨機的指定數量的城市坐標,保存到當前目錄的`cities.csv`文件中。
# * 如果需要本地數據,請在`main()`中修改文件路徑。
# * 相關參數在`main()`中可以修改。
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import time
# 生成城市坐標
city_num = 10 # 城市數量
name = ["city's name"] * city_num # 這個並沒什么用,但是不要省,省了的話還要修改代碼
x = [np.random.randint(0, 100) for i in range(city_num)]
y = [np.random.randint(0, 100) for i in range(city_num)]
with open("cities.csv", "w") as f:
for i in range(city_num):
f.write(name[i]+","+str(x[i])+","+str(y[i])+"\n")
f.write(name[0]+","+str(x[0])+","+str(y[0])+"\n") # 最后一個節點即為起點
# 打印城市的坐標
position = pd.read_csv("cities.csv", names=['ind','lat','lon'])
plt.scatter(x=position['lon'], y=position['lat'])
plt.show()
position.head()
def create_init_list(filename):
data = pd.read_csv(filename,names=['index','lat','lon']) # lat->緯度 lon->經度
data_list = []
for i in range(len(data)):
data_list.append([float(data.iloc[i]['lon']),float(data.iloc[i]['lat'])])
return data_list
def distance_matrix(coordinate_list, size): # 生成距離矩陣,鄰接矩陣
d = np.zeros((size + 2, size + 2))
for i in range(size + 1):
x1 = coordinate_list[i][0]
y1 = coordinate_list[i][1]
for j in range(size + 1):
if (i == j) or (d[i][j] != 0):
continue
x2 = coordinate_list[j][0]
y2 = coordinate_list[j][1]
distance = np.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)
if (i == 0): # 起點與終點是同一城市
d[i][j] = d[j][i] = d[size + 1][j] = d[j][size + 1] = distance
else:
d[i][j] = d[j][i] = distance
return d
def path_length(d_matrix, path_list, size): # 計算路徑長度
length = 0
for i in range(size + 1):
length += d_matrix[path_list[i]][path_list[i + 1]]
return length
def shuffle(my_list):#起點和終點不能打亂
temp_list=my_list[1:-1]
np.random.shuffle(temp_list)
shuffle_list=my_list[:1]+temp_list+my_list[-1:]
return shuffle_list
def product_len_probability(my_list,d_matrix,size,p_num): # population, d, size,p_num
len_list=[] # 種群中每個個體(路徑)的路徑長度
pro_list=[]
path_len_pro=[]
for path in my_list:
len_list.append(path_length(d_matrix,path,size))
max_len=max(len_list)+1e-10
gen_best_length=min(len_list) # 種群中最優路徑的長度
gen_best_length_index=len_list.index(gen_best_length) # 最優個體在種群中的索引
# 使用最長路徑減去每個路徑的長度,得到每條路徑與最長路徑的差值,該值越大說明路徑越小
mask_list=np.ones(p_num)*max_len-np.array(len_list)
sum_len=np.sum(mask_list) # mask_list列表元素的和
for i in range(p_num):
if(i==0):
pro_list.append(mask_list[i]/sum_len)
elif(i==p_num-1):
pro_list.append(1)
else:
pro_list.append(pro_list[i-1]+mask_list[i]/sum_len)
for i in range(p_num):
# 路徑列表 路徑長度 概率
path_len_pro.append([my_list[i],len_list[i],pro_list[i]])
# 返回 最優路徑 最優路徑的長度 每條路徑的概率
return my_list[gen_best_length_index],gen_best_length,path_len_pro
def choose_cross(population,p_num): # 隨機產生交配者的索引,越優的染色體被選擇幾率越大
jump=np.random.random() # 隨機生成0-1之間的小數
if jump<population[0][2]:
return 0
low=1
high=p_num
mid=int((low+high)/2)
# 二分搜索
# 如果jump在population[mid][2]和population[mid-1][2]之間,那么返回mid
while(low<high):
if jump>population[mid][2]:
low=mid
mid=(low+high) // 2
elif jump<population[mid-1][2]: # 注意這里一定是mid-1
high=mid
mid=(low+high) // 2
else:
return mid
def product_offspring(size, parent_1, parent_2, pm): # 產生后代
son = parent_1.copy()
product_set = np.random.randint(1, size+1)
parent_cross_set=set(parent_2[1:product_set]) # 交叉序列集合
cross_complete=1
for j in range(1,size+1):
if son[j] in parent_cross_set:
son[j]=parent_2[cross_complete]
cross_complete+=1
if cross_complete>product_set:
break
if np.random.random() < pm: #變異
son=veriation(son,size,pm)
return son
def veriation(my_list,size,pm):#變異,隨機調換兩城市位置
ver_1=np.random.randint(1,size+1)
ver_2=np.random.randint(1,size+1)
while ver_2==ver_1:#直到ver_2與ver_1不同
ver_2 = np.random.randint(1, size+1)
my_list[ver_1],my_list[ver_2]=my_list[ver_2],my_list[ver_1]
return my_list
def main(filepath, p_num, gen, pm):
start = time.time()
coordinate_list=create_init_list(filepath)
size=len(coordinate_list)-2 # 除去了起點和終點
d=distance_matrix(coordinate_list,size) # 各城市之間的鄰接矩陣
path_list=list(range(size+2)) # 初始路徑
# 隨機打亂初始路徑以建立初始種群路徑
population = [shuffle(path_list) for i in range(p_num)]
# 初始種群population以及它的最優路徑和最短長度
gen_best,gen_best_length,population=product_len_probability(population,d,size,p_num)
# 現在的population中每一元素有三項,第一項是路徑,第二項是長度,第三項是使用時轉盤的概率
son_list = [0] * p_num # 后代列表
best_path=gen_best # 最好路徑初始化
best_path_length=gen_best_length # 最好路徑長度初始化
every_gen_best=[gen_best_length] # 每一代的最優值
for i in range(gen): #迭代gen代
son_num=0
while son_num < p_num: # 循環產生后代,一組父母親產生兩個后代
father_index = choose_cross(population, p_num) # 獲得父母索引
mother_index = choose_cross(population, p_num)
father = population[father_index][0] # 獲得父母的染色體
mother = population[mother_index][0]
son_list[son_num] = product_offspring(size, father, mother, pm) # 產生后代加入到后代列表中
son_num += 1
if son_num == p_num:
break
son_list[son_num] = product_offspring(size, mother, father, pm) # 產生后代加入到后代列表中
son_num += 1
# 在新一代個體中找到最優路徑和最優值
gen_best, gen_best_length,population = product_len_probability(son_list,d,size,p_num)
if(gen_best_length < best_path_length): # 這一代的最優值比有史以來的最優值更優
best_path=gen_best
best_path_length=gen_best_length
every_gen_best.append(gen_best_length)
end = time.time()
print(f"迭代用時:{(end-start)}s")
print("史上最優路徑:", best_path, sep=" ")#史上最優路徑
print("史上最短路徑長度:", best_path_length, sep=" ")#史上最優路徑長度
# 打印各代最優值和最優路徑
x = [coordinate_list[point][0] for point in best_path] # 最優路徑各節點經度
y = [coordinate_list[point][1] for point in best_path] # 最優路徑各節點緯度
plt.figure(figsize=(8, 10))
plt.subplot(211)
plt.plot(every_gen_best) # 畫每一代中最優路徑的路徑長度
plt.subplot(212)
plt.scatter(x,y) # 畫點
plt.plot(x,y) # 畫點之間的連線
plt.grid() # 給畫布添加網格
plt.show()
if __name__ == '__main__':
filepath = r'cities.csv' # 城市坐標數據文件
p_num = 200 #種群個體數量
gen = 1000 #進化代數
pm = 0.1 #變異率
main(filepath, p_num, gen, pm)
運行結果:
迭代用時:3.543527126312256s
史上最優路徑: [0, 8, 1, 9, 7, 5, 4, 3, 2, 6, 10]
史上最短路徑長度: 303.83238696200436

