data文件夹 st70.tsp NAME: st70 TYPE: TSP COMMENT: 70-city problem (Smith/Thompson) DIMENSION: 70 EDGE_WEIGHT_TYPE : EUC_2D NODE_COORD_SECTION 1 64 96 2 80 39 3 69 23 4 72 42 5 48 67 6 58 43 7 81 34 8 79 17 9 30 23 10 42 67 11 7 76 12 29 51 13 78 92 14 64 8 15 95 57 16 57 91 17 40 35 18 68 40 19 92 34 20 62 1 21 28 43 22 76 73 23 67 88 24 93 54 25 6 8 26 87 18 27 30 9 28 77 13 29 78 94 30 55 3 31 82 88 32 73 28 33 20 55 34 27 43 35 95 86 36 67 99 37 48 83 38 75 81 39 8 19 40 20 18 41 54 38 42 63 36 43 44 33 44 52 18 45 12 13 46 25 5 47 58 85 48 5 67 49 90 9 50 41 76 51 25 76 52 37 64 53 56 63 54 10 55 55 98 7 56 16 74 57 89 60 58 48 82 59 81 76 60 29 60 61 17 22 62 5 45 63 79 70 64 9 100 65 17 82 66 74 67 67 10 68 68 48 19 69 83 86 70 84 94 EOF
import random
import math
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl # 在绘图中显示中文
class SA(object):
def __init__(self):
self.T0 = 4000# 当前温度
self.Tend = 1e-3#温度终值
self.rate = 0.9995# 降温系数
self.num_city=70
self.location = self.read_tsp('data/st70.tsp')#城市坐标,class 'numpy.ndarray',二维数组
self.dis_mat = self.compute_dis_mat()#距离矩阵
self.path = self.greedy_init(self.dis_mat,100,self.num_city)#一维列表(使用贪婪算法获得一个较好的初始解)
# self.path = self.random_init(self.num_city) # 一维列表(随机初始化一个解)
self.iter_x = [0]#外层循环代数
self.iter_y = [self.compute_pathlen(self.path)]#每个温度下的最优路径的长度
self.count = 0 # 外层循环次数
def greedy_init(self, dis_mat, num_total, num_city):
"""使用贪婪算法获得一个较好的初始解 """
start_index = 0
result = []
for i in range(num_total):
rest = [x for x in range(0, num_city)]
# 所有起始点都已经生成了
if start_index >= num_city:
start_index = np.random.randint(0, num_city)# todo 是不是要减去1
result.append(result[start_index].copy())
continue
current = start_index
rest.remove(current)
# 找到一条最近邻路径
result_one = [current]
while len(rest) != 0:
tmp_min = math.inf
tmp_choose = -1
for x in rest:
if dis_mat[current][x] < tmp_min:
tmp_min = dis_mat[current][x]
tmp_choose = x
current = tmp_choose
result_one.append(tmp_choose)
rest.remove(tmp_choose)
result.append(result_one)
start_index += 1
length=[]
for i in result:
length.append(self.compute_pathlen(i))
# pathlens = self.compute_paths(result)
sortindex = np.argsort(length)
index = sortindex[0]
return result[index]
# 初始化一条随机路径
def random_init(self, num_city):
tmp = [x for x in range(num_city)]
random.shuffle(tmp)
return tmp
# 计算不同城市之间的距离
def compute_dis_mat(self):
dis_mat = np.zeros((self.num_city, self.num_city))
for i in range(self.num_city):
for j in range(self.num_city):
if i == j:
dis_mat[i][j] = np.inf
continue
a = self.location[i]
b = self.location[j]
tmp = np.sqrt(sum([(x[0] - x[1]) ** 2 for x in zip(a, b)]))
dis_mat[i][j] = tmp
return dis_mat
# 计算路径长度
def compute_pathlen(self, path):
length=0
for i in range(len(path) - 1):
a = path[i]
b = path[i + 1]
length += self.dis_mat[a][b]
length+=self.dis_mat[path[0]][path[-1]]
return length
# 产生一个新解:随机选取一个片段,然后把片段倒序,再安插在原位置
def get_new_path(self, path):
path = path.copy()
t = [x for x in range(len(path))]
a, b = np.random.choice(t, 2)
path[a:b] = path[a:b][::-1]
return path
# 退火策略,根据温度变化有一定概率接受差的解
def eval_fire(self, best_path, new_path):
best_length = self.compute_pathlen(best_path)
new_length = self.compute_pathlen(new_path)
dc = new_length - best_length
# p = max(1e-1, np.exp(-dc / self.T0))
p = np.exp(-dc / self.T0)
if new_length < best_length:
return new_path, new_length
elif np.random.rand() <= p:
return new_path, new_length
else:
return best_path, best_length
# if new_length < best_length:
# return new_path, new_length
# else:
# if np.random.rand() <= p:
# return new_path, new_length
# else:
# return best_path, best_length
def read_tsp(self, path):# 读取数据
lines = open(path, 'r').readlines()
assert 'NODE_COORD_SECTION\n' in lines
index = lines.index('NODE_COORD_SECTION\n')
data = lines[index + 1:-1]
tmp = []
for line in data:
line = line.strip().split(' ')
if line[0] == 'EOF':
continue
tmpline = []
for x in line:
if x == '':
continue
else:
tmpline.append(float(x))
if tmpline == []:
continue
tmp.append(tmpline)
data = tmp
data = np.array(data) # 将列表转换成数组
data = data[:, 1:]
return data
# 模拟退火总流程
def run(self):
# 记录最优解
best_path = self.path#路径
best_length = self.compute_pathlen(best_path)#长度
while self.T0 > self.Tend:
self.count += 1
# 产生在这个温度下的随机解
new_path = self.get_new_path(self.path.copy())
# 根据温度判断是否选择这个解
self.path, length = self.eval_fire(best_path, new_path)
# 更新最优解
if length < best_length:
best_length = length
best_path = self.path
# 降低温度
self.T0 *= self.rate# 温度按照一定的比例下降(冷却)
# 记录路径收敛曲线
self.iter_x.append(self.count)
self.iter_y.append(best_length)
# print(self.count, best_length)
print(f"遍历{self.num_city}个城市所需的最短时间为{best_length}")
print(f"对应的具体路径为{best_path}")
path_loc=self.location[best_path]#self.location[best_path]依然是二维数组,把self.location数组按照best_path排序
mpl.rcParams["font.sans-serif"] = ["Arial Unicode MS"] # 设置中文显示字体
mpl.rcParams["axes.unicode_minus"] = False # 设置正常显示符号,负号显示解决方案
fig,axs = plt.subplots(2, 1, sharex=False, sharey=False)
axs[0].scatter(path_loc[:, 0], path_loc[:, 1])#散点图
path_loc = np.vstack([path_loc, path_loc[0]])# 加上一行因为会回到起点#71*2
axs[0].plot(path_loc[:, 0], path_loc[:, 1])
axs[0].set_title('规划结果')
iterations = self.iter_x#外层循环代数,列表
best_record = self.iter_y#每个温度下的最优路径的长度,列表
axs[1].plot(iterations, best_record)
axs[1].set_title('收敛曲线')
plt.show()
instance = SA()
instance.run()