两种写法思路,最全备注,第二种个人感觉上理解起来稍容易一点:
第一种:
import numpy as np
import matplotlib.pyplot as plt
import pdb
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['KaiTi'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
"旅行商问题 ( TSP , Traveling Salesman Problem )"
coordinates = np.array([[565.0, 575.0], [25.0, 185.0], [345.0, 750.0], [945.0, 685.0], [845.0, 655.0],
[880.0, 660.0], [25.0, 230.0], [525.0, 1000.0], [580.0, 1175.0], [650.0, 1130.0],
[1605.0, 620.0], [1220.0, 580.0], [1465.0, 200.0], [1530.0, 5.0], [845.0, 680.0],
[725.0, 370.0], [145.0, 665.0], [415.0, 635.0], [510.0, 875.0], [560.0, 365.0],
[300.0, 465.0], [520.0, 585.0], [480.0, 415.0], [835.0, 625.0], [975.0, 580.0],
[1215.0, 245.0], [1320.0, 315.0], [1250.0, 400.0], [660.0, 180.0], [410.0, 250.0],
[420.0, 555.0], [575.0, 665.0], [1150.0, 1160.0], [700.0, 580.0], [685.0, 595.0],
[685.0, 610.0], [770.0, 610.0], [795.0, 645.0], [720.0, 635.0], [760.0, 650.0],
[475.0, 960.0], [95.0, 260.0], [875.0, 920.0], [700.0, 500.0], [555.0, 815.0],
[830.0, 485.0], [1170.0, 65.0], [830.0, 610.0], [605.0, 625.0], [595.0, 360.0],
[1340.0, 725.0], [1740.0, 245.0]])
# 得到距离矩阵的函数
def getdistmat(coordinates):
num = coordinates.shape[0] # 52个坐标点
distmat = np.zeros((52, 52)) # 52X52距离矩阵
for i in range(num):
for j in range(i, num):
distmat[i][j] = distmat[j][i] = np.linalg.norm(coordinates[i] - coordinates[j])
return distmat
# 定义一个得到初始的参数的方法
def initpara():
alpha = 0.99 # 温度衰减系数
t = (1, 100) # 元组
markovlen = 1000 # 马尔可夫链
return alpha, t, markovlen
num = coordinates.shape[0] # 得到坐标点数量--52;shape[0]就是读取矩阵第一维度的长度。直接用.shape可以快速读取矩阵的形状
distmat = getdistmat(coordinates) # 得到距离矩阵
# 经过变换产生的新解,后面还需要判断是否被接受代替当前解solutioncurrent
solutionnew = np.arange(num) # arange()返回一个有终点和起点的固定步长的排列,一个参数时起点默认0,重点参数值,步长默认1
# valuenew = np.max(num)
#当前现行的解决方式-城市路径
solutioncurrent = solutionnew.copy()
valuecurrent = 99000 # np.max这样的源代码可能同样是因为版本问题被当做函数不能正确使用,应取一个较大值作为初始值
# 记录当前的最优路线方式及最优的值(适应度/距离)
solutionbest = solutionnew.copy()
valuebest = 99000 # np.max
alpha, t2, markovlen = initpara()
t = t2[1] # t=初始温度100
result = [] # 记录迭代过程中的最优解
while t > t2[0]: # 判断当前温度仍然高于设定的最终温度
for i in np.arange(markovlen): # 每一代的温度下都要循环执行马尔可夫链次数-1000
# 下面的两交换和三角换是两种扰动方式,用于产生新解
if np.random.rand() > 0.5: # 交换路径中的这2个节点的顺序
# np.random.rand()产生[0, 1)区间的均匀随机数
while True: # 产生两个不同的随机数
loc1 = np.int(np.ceil(np.random.rand() * (num - 1)))
loc2 = np.int(np.ceil(np.random.rand() * (num - 1)))
if loc1 != loc2:
break
solutionnew[loc1], solutionnew[loc2] = solutionnew[loc2], solutionnew[loc1]
else: # 三交换
while True:
loc1 = np.int(np.ceil(np.random.rand() * (num - 1)))
loc2 = np.int(np.ceil(np.random.rand() * (num - 1)))
loc3 = np.int(np.ceil(np.random.rand() * (num - 1)))
if ((loc1 != loc2) & (loc2 != loc3) & (loc1 != loc3)):
break
# 下面的三个判断语句使得loc1 loc2:
loc1, loc2 = loc2, loc1
if loc2 > loc3:
loc2, loc3 = loc3, loc2
if loc1 > loc2:
loc1, loc2 = loc2, loc1
# 下面的三行代码将[loc1,loc2)区间的数据插入到loc3之后
tmplist = solutionnew[loc1:loc2].copy()
solutionnew[loc1:loc3 - loc2 + 1 + loc1] = solutionnew[loc2:loc3 + 1].copy()
solutionnew[loc3 - loc2 + 1 + loc1:loc3 + 1] = tmplist.copy()
valuenew = 0
for i in range(num - 1):
valuenew += distmat[solutionnew[i]][solutionnew[i + 1]]
valuenew += distmat[solutionnew[0]][solutionnew[51]] # 计算经过变换的新路径的长度,最后需要再回到出发城市
if valuenew < valuecurrent: # 接受该解
# 更新solutioncurrent 和solutionbest
valuecurrent = valuenew
solutioncurrent = solutionnew.copy()
if valuenew < valuebest: #判断是否需要对目前的最优解进行更新
valuebest = valuenew
solutionbest = solutionnew.copy()
else: # 按一定的概率接受该解
if np.random.rand() < np.exp(-(valuenew - valuecurrent) / t):
valuecurrent = valuenew
solutioncurrent = solutionnew.copy()
else:
solutionnew = solutioncurrent.copy()
t = alpha * t # 应用温度衰减系数来进行逐步降温
result.append(valuebest)
print(t) # 程序运行时间较长,打印t来监视程序进展速度--也就是当前的温度
# 用来显示结果
plt.plot(np.array(result))
plt.ylabel("最佳值")
plt.xlabel("时间")
plt.show()
第二种:
import math
import random
import numpy as np
import matplotlib.pyplot as plt
# 随机生成城市信息
# nCity = 10
# City = np.random.uniform(-10, 10, [nCity, 2]) # uniform()生成10个二维数组,数值范围是-10到10
# 采用和1中相同的数据集
nCity = 52
City = np.array([[565.0, 575.0], [25.0, 185.0], [345.0, 750.0], [945.0, 685.0], [845.0, 655.0],
[880.0, 660.0], [25.0, 230.0], [525.0, 1000.0], [580.0, 1175.0], [650.0, 1130.0],
[1605.0, 620.0], [1220.0, 580.0], [1465.0, 200.0], [1530.0, 5.0], [845.0, 680.0],
[725.0, 370.0], [145.0, 665.0], [415.0, 635.0], [510.0, 875.0], [560.0, 365.0],
[300.0, 465.0], [520.0, 585.0], [480.0, 415.0], [835.0, 625.0], [975.0, 580.0],
[1215.0, 245.0], [1320.0, 315.0], [1250.0, 400.0], [660.0, 180.0], [410.0, 250.0],
[420.0, 555.0], [575.0, 665.0], [1150.0, 1160.0], [700.0, 580.0], [685.0, 595.0],
[685.0, 610.0], [770.0, 610.0], [795.0, 645.0], [720.0, 635.0], [760.0, 650.0],
[475.0, 960.0], [95.0, 260.0], [875.0, 920.0], [700.0, 500.0], [555.0, 815.0],
[830.0, 485.0], [1170.0, 65.0], [830.0, 610.0], [605.0, 625.0], [595.0, 360.0],
[1340.0, 725.0], [1740.0, 245.0]])
Dis = {}
for i in range(nCity): # range() 创建一个整数列表
for j in range(nCity):
if i > j:
dis = ((City[i][0] - City[j][0]) ** 2 + (City[i][1] - City[j][1]) ** 2) ** 0.5
Dis[(i, j)] = dis
Dis[(j, i)] = dis
Data_BestFit = [] # 用于保存每一代蚂蚁的最优适应度
# 适应度计算函数 适应值 = 路径距离
def Cal_Fit(X):
total_dis = Dis[(X[-1], X[0])]
for i in range(nCity - 1):
total_dis += Dis[(X[i], X[i + 1])]
return total_dis
# 解决问题的函数来了
def SAA_TSP():
# 定义参数
T0 = 100 # 初始温度
T1 = 0.1 # 最终温度
a = 0.99 # 温度衰减系数
Markov = 100 # 马尔科夫链
Pm = 0.3 # 变异概率
Pc = 0.7 # 交叉概率
# 初始化解
Best_X = None # 最好的路线
Best_Fit = math.inf # 最好的适应度=路径距离,所以是越小越好 math.inf返回一个浮点数,表示正无穷大
X = np.zeros([Markov, nCity]) # zeros()返回一个给定形状和类型的新数组,其中包含0--返回的数组:100个52个0组成的数组
Fit = np.zeros([Markov]) # 返回的数组由100个0组成
for i in range(Markov):
X[i] = np.random.permutation(nCity) # permutation()返回数组:52个(0-51)随机打乱的数,即随机的城市顺序
Fit[i] = Cal_Fit(X[i]) # 计算这个随机生成的城市序列的距离也就是适应度
if Fit[i] < Best_Fit: # 如果距离比目前最好的路径还短则对最优解进行更新,得到初始解以及其中的最优解
Best_Fit = Fit[i]
Best_X = X[i].copy()
# 降温过程
T = T0
while T >= T1:
for i in range(Markov):
# 变异、交叉产生新解
x = X[i].copy()
if random.random() < Pm: # 变异
point1 = int(random.random() * nCity) # 随机选择第一个城市节点
point2 = int(random.random() * nCity) # 随机选择第二个城市节点
while point1 == point2: # 保证选到的两个城市节点是不同的,如果相同的话重新选择,选到不一样的为止
point1 = int(random.random() * nCity)
point2 = int(random.random() * nCity)
x[point1], x[point2] = x[point2], x[point1] #选中的两个城市互相交换顺序
if random.random() < Pc: # 交叉
point = int(random.random() * nCity) # 随机得到一个节点
temp1 = list(x[0:point]) # 第一段:0-point
temp2 = list(x[point:nCity]) #第二段:point到最后一个城市
temp2.extend(temp1) # 将第一段放到第二段后面
x = np.array(temp2.copy()) # 通过array把列表转化为数组
# 计算新解适应值并判断是否接受
fit = Cal_Fit(x)
delta = fit - Fit[i]
if delta <= 0: # 判断新解适应度是不是更好
Fit[i] = fit # 经过变异交叉后的解更好,做更新替代
X[i] = x
if Fit[i] < Best_Fit: # 判断新解和目前的最优解比是个什么概念 需不需要更新
Best_Fit = Fit[i]
Best_X = X[i].copy()
# 是否接受恶化解
elif random.random() < math.exp(-delta / T):
Fit[i] = fit
X[i] = x.copy()
Data_BestFit.append(Best_Fit) # 保存在这个温度下的100个解中最好的是多少
T *= a # 降温
return Best_X, Best_Fit
# 绘制路径与迭代曲线
def Draw_City(City, X, Best_Fit):
X = list(X)
X.append(X[0]) # 再最后再回到出发的城市
coor_x = []
coor_y = []
for i in X:
i = int(i)
coor_x.append(City[i][0]) # 按照最优路径顺序将所有城市的x轴坐标写入coor_x中
coor_y.append(City[i][1])
plt.plot(coor_x, coor_y, 'r-o') # 这里的r-o参数代表了对颜色、点型、线型的设置
plt.title('TSP with Simulated Annealing Algorithm\n' + 'total_dis = ' + str(round(Best_Fit, 3)))
plt.show()
# print(len(Data_BestFit)) # 想知道一共走了多少个温度
plt.plot(range(len(Data_BestFit)), Data_BestFit)
plt.title('Iteration_BestFit')
plt.xlabel('iteration')
plt.ylabel('fitness')
plt.show()
if __name__ == '__main__':
# 右键执行时默认的执行入口
Best_X, Best_Fit = SAA_TSP()
Draw_City(City, Best_X, Best_Fit)