遺傳演算法解決TSP旅行商問題(附:Python實現)
阿新 • • 發佈:2018-12-19
前言
我先囉嗦一下:不是很喜歡寫計算智慧的演算法,因為一個演算法就要寫好久。前前後後將近有兩天的時間。 好啦,現在進入正題。
巡迴旅行商問題(TSP)是一個組合優化方面的問題,已經成為測試組合優化新演算法的標準問題。應用遺傳演算法解決 TSP 問題,首先對訪問城市序列進行排列組合的方法編碼,這保證了每個城市經過且只經過一次。接著生成初始種群,並計算適應度函式,即計算遍歷所有城市的距離。然後用最優儲存法確定選擇運算元,以保證優秀個體直接複製到下一代。採用有序交叉和倒置變異法確定交叉運算元和變異運算元。
演算法流程
旅行商問題的遺傳演算法實現
- 初始群體設定 一般都是隨機生成一個規模為 N 的初始群體。在這裡,我們定義一個s行t列的pop矩陣來表示群體,t 為城市個數 + 1,即 N + 1,s 為樣本中個體數目。在本文探討了 30 個城市的 TSP 問題,此時 t 取值 31,該矩陣中每一行的前 30 個元素表示經過的城市編號,最後一個元素表示適應度函式的取值,即每個個體所求的距離。
- 適應度函式的設計是根據個體適應值對其優劣判定的評價函式。在該問題中用距離的總和作為適應度函式,來衡量求解結果是否最優。
- 選擇指以一定的概率從群體中選擇優勝個體的操作,它是建立在群體中個體適應度評估基礎上的。為了加快區域性搜尋的速度,在演算法中採用最優儲存策略的方法,即將群體中適應度最大的個體直接替換適應度最小的個體。它們不進行交叉和變異運算,而是直接複製到下一代,以免交叉和變異運算破壞種群中的優秀解答。
- 交叉運算元是產生新個體的主要手段。它是指將個體進行兩兩配對,以交叉概率 Pc 將配對的父代個體的部分結構加以替換重組生成新個體的操作。本文中採用有序交叉法來實現。有序交叉法的步驟描述如下:
程式碼實現
from itertools import permutations import numpy as np import matplotlib import matplotlib.pyplot as plt from itertools import combinations, permutations %matplotlib inline
def fitnessFunction(pop,num,city_num,x_position_add_end,y_position_add_end):
'''適應度函式,計算每個排列的適應度,並儲存到pop矩陣第二維的最後一項'''
for x1 in range(num):
square_sum = 0
for x2 in range(city_num):
square_sum += (x_position_add_end[int(pop[x1][x2])] - x_position_add_end[int(pop[x1][x2+1])])**2 + (y_position_add_end[int(pop[x1][x2])] - y_position_add_end[int(pop[x1][x2+1])])**2
# print(round(1/np.sqrt(square_sum),7))
pop[x1][-1] = round(1/np.sqrt(square_sum),7)
def choiceFuction(pop):
'''
這裡的做法:比如A當前種群中的最優解,B為經過交叉、變異後的最差解,把A作為最當前代中的最優解儲存下來作為這一代的最優解,同時A也參與交叉
和變異。經過交叉、變異後的最差解為B,那麼我再用A替代B。
:argument pop矩陣
:return 本代適應度最低的個體的索引值和本代適應度最高的個體
'''
yield np.argmin(pop[:, -1])
yield pop[np.argmax(pop[:, -1])]
def choice(pop,num,city_num,x_position_add_end,y_position_add_end,b):
fitnessFunction(pop,num,city_num,x_position_add_end,y_position_add_end)
c,d =choiceFuction(pop)
# 上一代的最優值替代本代中的最差值
pop[c] = b
return pop
def drawPic(maxFitness,x_position,y_position,i):
index = np.array(maxFitness[:-1],dtype=np.int32)
x_position_add_end = np.append(x_position[index],x_position[[index[0]]])
y_position_add_end = np.append(y_position[index],y_position[[index[0]]])
fig = plt.figure()
plt.plot(x_position_add_end,y_position_add_end,'-o')
plt.xlabel('x',fontsize = 16)
plt.ylabel('y',fontsize = 16)
plt.title('{iter}'.format(iter=i))
以下的交叉計算應該是用遞迴的演算法實現比較合理,但本人能力有限。有大佬看得懂我亂七八糟的程式碼的,請給我提提建議。
def matuingFuction(pop,pc,city_num,pm,num):
mating_matrix =np.array(1-(np.random.rand(num)>pc),dtype=np.bool) # 交配矩陣,如果為true則進行交配
a = list(pop[mating_matrix][:,:-1])# 進行交配的個體
b = list(pop[np.array(1-mating_matrix,dtype=bool)][:,:-1]) # 未進行交配的個體,直接放到下一代
b = [list(i) for i in b] # 對b進行型別轉換,避免下面numpy.array 沒有index屬性
# print(a)
if len(a)%2 !=0:
b.append(a.pop())
# print('ab的長度:',len(a),len(b))
for i in range(int(len(a)/2)):
# 隨機初始化兩個交配點,這裡寫得不好,這邊的兩個點初始化都是一個在中間位置偏左,一個在中間位置偏右
p1 = np.random.randint(1,int(city_num/2)+1)
p2 = np.random.randint(int(city_num/2)+1,city_num)
x1 = list(a.pop())
x2 = list(a.pop())
matuting(x1,x2,p1,p2)
# 交配之後產生的個體進行一定概率上的變異
variationFunction(x1,pm,city_num)
variationFunction(x2,pm,city_num)
b.append(x1)
b.append(x2)
zero = np.zeros((num,1))
# print('b的形狀:',len(b))
temp = np.column_stack((b, zero))
return temp
def matuting(x1,x2,p1,p2):
# 以下進行交配
# 左邊交換位置
temp = x1[:p1]
x1[:p1] = x2[:p1]
x2[:p1] = temp
# 右邊交換位置
temp = x1[p2:]
x1[p2:] = x2[p2:]
x2[p2:] = temp
# 尋找重複的元素
center1 = x1[p1:p2]
center2 = x2[p1:p2]
while True: # x1左邊
for i in x1[:p1]:
if i in center1:
# print(center1.index(i)) # 根據值找到索引
x1[x1[:p1].index(i)] = center2[center1.index(i)]
break
if np.intersect1d(x1[:p1],center1).size == 0: # 如果不存在交集,則迴圈結束
break
while True: # x1右邊
for i in x1[p2:]:
if i in center1:
# print(center1.index(i)) # 根據值找到索引
x1[x1[p2:].index(i) + p2] = center2[center1.index(i)]
# print(x1)
break
if np.intersect1d(x1[p2:],center1).size == 0: # 如果不存在交集,則迴圈結束
break
while True: # x2左邊
for i in x2[:p1]:
if i in center2:
# print(center2.index(i)) # 根據值找到索引
x2[x2[:p1].index(i)] = center1[center2.index(i)]
break
if np.intersect1d(x2[:p1],center2).size == 0: # 如果不存在交集,則迴圈結束
break
while True: # x2右邊
for i in x2[p2:]:
if i in center2:
# print(center2.index(i)) # 根據值找到索引
x2[x2[p2:].index(i) + p2] = center1[center2.index(i)]
# print(x2)
break
if np.intersect1d(x2[p2:],center2).size == 0: # 如果不存在交集,則迴圈結束
break
def variationFunction(list_a,pm,city_num):
'''變異函式'''
if np.random.rand() < pm:
p1 = np.random.randint(1,int(city_num/2)+1)
p2 = np.random.randint(int(city_num/2)+1,city_num)
# print(p1,p2)
temp = list_a[p1:p2]
temp.reverse()
list_a[p1:p2] = temp
# print(list_a)
def main():
# 初始化
pop = [] # 存放訪問順序和每個個體適應度
num = 250 # 初始化群體的數目
city_num = 10 # 城市數目
pc = 0.9 # 每個個體的交配概率
pm = 0.2 # 每個個體的變異概率
x_position = np.random.randint(0,100,size=city_num)
y_position = np.random.randint(0,100,size=city_num)
x_position_add_end = np.append(x_position,x_position[0])
y_position_add_end = np.append(y_position,y_position[0])
for i in range(num):
pop.append(np.random.permutation(np.arange(0,city_num))) # 假設有5個城市,初始群體的數目為60個
# 初始化化一個60*1的拼接矩陣,值為0
zero = np.zeros((num,1))
pop = np.column_stack((pop, zero)) # 矩陣的拼接
fitnessFunction(pop,num,city_num,x_position_add_end,y_position_add_end)
for i in range(180):
a,b = choiceFuction(pop) # a 為當代適應度最小的個體的索引,b為當代適應度最大的個體,這邊要保留的是b
# print('索引值和適應度最大的個體:',a,b)
# pop[a]=b
if (i+1)%10==0:
drawPic(b,x_position,y_position,i+1) # 根據本代中的適應度最大的個體畫圖
pop_temp = matuingFuction(pop,pc,city_num,pm,num) #交配變異
pop = choice(pop_temp,num,city_num,x_position_add_end,y_position_add_end,b)
main()
分析 迭代了180次,這個演算法有了一定的效果,可能是迭代的次數不夠,繼續迭代可能會呈現更好的結果。但是在其中的執行過程中,發現迭代次數較多的情況下,有可能還會使得路程變遠,偏離了一個較好的狀態是不是演算法實現存在一定問題或者是變異導致,這邊沒有再做更加具體研究,因為本科生當前時間有限,沒辦法花更多的時間,待日後繼續更改。
執行結果:
參考: 基於遺傳演算法的旅行商問題的研究