遺傳演算法求解TSP問題(python版)

xieju0605發表於2020-11-10

簡介

改進和實現遺傳演算法,用以對旅行商問題(TSP問題)進行建模和近似求解,從而深入對啟發式演算法的理解。

演算法流程

遺傳演算法解決TSP的流程是以下幾部分:初始化種群、計算適應度函式、選擇、交叉、變異然後不斷重複直到找到理想的解。

模型設定

I 種群初始化。

需要設定的引數是隨機生成的初始解的數量,該數量過少會導致種群多樣性不足,數量過多會降低演算法的效率,我們設定種群規模(初始解數量為150)。

II 適應度函式。

根據資料集說明,其最優解採用的邊權重型別為:EDGE_WEIGHT_TYPE : EUC_2D,即兩城市之間的距離通過歐式距離計算。
在這裡插入圖片描述

我們得到對路徑的所有距離進行求和得到distance,令f=1/distance,即為適應度函式。

III 選擇

選擇,即在上一代生存的個體中,通過優勝劣汰,使適應性更強的解得以保留。具體而言首先將上一代種群中適應性最強的10%物種保留,然後通過輪盤轉賭法,以選擇概率為權重,挑出剩下的90%物種。
其中對於每個物種s_i)選擇概率計算公式為:
在這裡插入圖片描述

採用上述設定的原因是儘量讓適應度更強的物種活下來,同時防止適應性最強的物種因隨機性而被輪盤轉賭法淘汰。

IV 交叉

通過選擇倖存下的物種進行交叉的概率為70%,交叉的方式為單點交叉,即隨機選取一個節點,將交叉雙方該節點後的部分進行交換。在交換後,單個物種可能會出現有重複城市的情況,因此我們進行了去重操作,即記錄下重複的位置,使交叉雙方重複的節點進行交換。

V 變異

變異是遺傳演算法跳出區域性最優解的重要操作。在TSP問題中,變異操作是隨機選取物種的兩個節點,將節點中的城市順序顛倒。過往的研究表明,變異的概率大於0.5之後,遺傳演算法將退化為隨機搜尋。但考慮到跳出區域性最優解的重要性,因此我們設定變異的概率為20%。

實驗結果

算例一:

(1) 算例名稱:DJ38,城市:38,最短距離:6656
來源:http://www.math.uwaterloo.ca/tsp/world/countries.html#DJ

(2) 最優解:
29->28->20->13->9->0->1->3->2->4->5->6->7->8->11->10->18->17->16->15->12->14->19->22->25->24->21->23->27->26->30->35->33->32->37->36->34->31->29

(3) 視覺化結果
上部分圖片為官方給的路線圖,下部分圖片為我們求得的最優解。
在這裡插入圖片描述在這裡插入圖片描述
(4) 演算法求得的最短距離 隨 求解時間 變化結果
在這裡插入圖片描述
(5) 解的質量

Gap=(6659.43 - 6656) / 6656 = 0.05%

算例二

(1) 算例名稱:TSPLIB , qa194,城市:194,最短距離:9352
來源:http://www.math.uwaterloo.ca/tsp/world/countries.html#DJ

(2) 最優解路線:
143->149->153->156->163->162->160->155->144->148->145->138->137->141->139->136->133->131->129->126->124->125->113->110->103->100->98->93->97->89->88->81->61->58->35->62->84->85->64->19->0->5->7->15->12->22->24->16->13->10->6->3->1->2->4->8->9->11->14->18->29->31->30->34->37->40->45->43->41->49->48->54->53->51->52->55->47->42->39->33->38->26->36->50->46->57->60->66->72->65->67->63->69->76->83->80->78->82->87->92->95->94->91->96->99->109->111->107->106->104->105->102->90->73->68->59->56->44->28->21->27->32->17->20->23->25->71->77->74->75->70->79->86->101->108->112->118->121->117->130->128->120->116->115->114->119->122->123->127->132->134->142->147->159->165->170->184->192->180->183->187->188->190->191->189->193->181->175->168->171->178->185->186->182->173->172->174->176->177->179->169->161->166->167->164->158->157->154->135->150->146->151->152->140->143

(3) 視覺化結果
上部分圖片為官方給的路線圖,下部分圖片為我們求得的最優解。
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201110231636375.png#pic_center
在這裡插入圖片描述

在這裡插入圖片描述

(4)演算法求得的最短距離 隨 求解時間 變化結果
在這裡插入圖片描述
(5)解的質量

Gap=(10087.017 - 9352) / 9352 = 7.86%

總結

在城市數量較少時,該演算法的精度較高,且收斂速度較快。當城市數量多時,演算法容易收斂到區域性最小值。

實驗程式碼

# -*- coding: utf-8 -*-
"""
Created on Wed Jun  5 18:06:59 2019

@author: 1
"""
#匯入需要用到的包
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#from numba import jit
#初始化引數
species = 200
iters = 5000

def getListMaxNumIndex(num_list,topk=int(0.2*species)):
    '''
    獲取列表中最大的前n個數值的位置索引
    '''
    num_dict = {}
    for i in range(len(num_list)):
        num_dict[i] = num_list[i]
    res_list = sorted(num_dict.items(),key=lambda e:e[1])
    max_num_index = [one[0] for one in res_list[::-1][:topk]]
    return max_num_index

#適應度函式
def calfit(trip, num_city):
    total_dis = 0
    for i in range(num_city):
        cur_city = trip[i]
        next_city = trip[i+1] % num_city
        temp_dis = distance[cur_city][next_city]
        total_dis = total_dis + temp_dis    
    return 1 / total_dis

def dis(trip, num_city):
    total_dis = 0
    for i in range(num_city):
        cur_city = trip[i]
        next_city = trip[i+1] % num_city
        temp_dis = distance[cur_city][next_city]
        total_dis = total_dis + temp_dis   
    return total_dis
#交叉函式
def crossover(father,mother):
    num_city = len(father)
    #indexrandom = [i for i in range(int(0.4*cronum),int(0.6*cronum))]
    index_random = [i for i in range(num_city)]
    pos = random.choice(index_random)
    son1 = father[0:pos]
    son2 = mother[0:pos]
    son1.extend(mother[pos:num_city])
    son2.extend(father[pos:num_city])
    
    index_duplicate1 = []
    index_duplicate2 = []
    
    for i in range(pos, num_city):
        for j in range(pos):
            if son1[i] == son1[j]:
                index_duplicate1.append(j)
            if son2[i] == son2[j]:
                index_duplicate2.append(j)
    num_index = len(index_duplicate1)
    for i in range(num_index):
        son1[index_duplicate1[i]], son2[index_duplicate2[i]] = son2[index_duplicate2[i]], son1[index_duplicate1[i]]
    
    return son1,son2

#變異函式
def mutate(sample):
    num_city = len(sample)
    part = np.random.choice(num_city,2,replace=False)
    if part[0] > part[1]:
        max_ = part[0]
        min_ = part[1]
    else:
        max_ = part[1]
        min_ = part[0]
    after_mutate = sample[0:min_]
    temp_mutate = list(reversed(sample[min_:max_]))
    after_mutate.extend(temp_mutate)
    after_mutate.extend(sample[max_:num_city])
    return after_mutate



#讀取城市位置資料
import datetime

starttime = datetime.datetime.now()

#long running


df1 = pd.read_csv('size38.txt', sep=' ', header=None)
#df1 = pd.read_csv('size29.txt', sep=' ', header=None)
#df1 = pd.read_csv('size194.txt', sep=' ', header=None)
#df1 = pd.read_csv('size131.txt', sep=' ', header=None)
#df2 = pd.read_csv('st70.txt', sep=' ', header=None)
df1[0] = df1[0] - 1
plot = plt.plot(df1[1], df1[2], '*')

#計算各城市鄰接矩陣。
n = len(df1)
distance = np.zeros((n, n))
for i in range(n):
    for j in range(n):
        temp1 = np.power((df1.iloc[i,1] - df1.iloc[j,1]),2)
        temp2 = np.power((df1.iloc[i,2] - df1.iloc[j,2]),2)
        distance[i][j] = np.sqrt(temp1 + temp2)

#初始化種群,生成可能的解的集合
x = []
counter = 0
#for i in range(species):
while counter < species:
    dna = np.random.permutation(range(n)).tolist()
    start = dna[0]
    dna.append(start)
    if dna not in x:
        x.append(dna)
        counter = counter + 1

ctlist = []
dislist = []
ct = 0
while ct < iters: 
    ct = ct + 1    
    f = []
    for i in range(species):    
        f.append(calfit(x[i], n))
    
    
    #計算選擇概率
    sig = sum(f)
    p = (f / sig).tolist()
    
    test = getListMaxNumIndex(p)
    testnum = len(test)
    newx = []
    for i in range(testnum):
        newx.append(x[test[i]])
        #newx.append(x[test[i]])
    index = [i for i in range(species)]
    news = random.choices(index,weights=p,k=int(0.8*species))
    newsnum = len(news)
    for i in range(newsnum):
        newx.append(x[news[i]])
        
    m = int(species/2)
    for i in range(0,m):
        j = i + m - 1
        #j=i+1
        numx = len(newx[0])
        if random.choice([1,2,3,4,5,6,7,8,9,10]) < 8:
            tplist1 = newx[i][0:numx-1]
            tplist2 = newx[j][0:numx-1]
            crosslist1,crosslist2 = crossover(tplist1,tplist2)
            if random.choice([1,2,3,4,5,6,7,8,9,10]) < 4:
                crosslist1 = mutate(crosslist1)
                crosslist2 = mutate(crosslist2)
            end1 = crosslist1[0]
            end2 = crosslist2[0]
            crosslist1.append(end1)
            crosslist2.append(end2)
            newx[i] = crosslist1
            newx[j] = crosslist2
    x = newx
    res = []
    for i in range(species):    
        res.append(calfit(x[i], n))  
    result = 1 / max(res)
    res1 = []
    for i in range(species):    
        res1.append(dis(x[i], n))  
    result1 = min(res1)
    print(ct)
    print(result)
    print(result1)
    ctlist.append(ct)
    dislist.append(result)

endtime = datetime.datetime.now()

print (endtime - starttime)

plk1 = []
plk2 = []
for i in range(len(x[0])):
    plk2.append(df1.iloc[x[0][i], 2])
    plk1.append(df1.iloc[x[0][i], 1])
plot = plt.plot(plk1, plk2, c='r')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
    
plot = plt.plot(ctlist, dislist)
plt.xlabel('iters')
plt.ylabel('distance')
plt.show()

需要測試資料請聯絡博主,或者去我文中提供的來源自行下載。

相關文章