(Python)模拟退火算法解决旅行商问题(TSP)

两种写法思路,最全备注,第二种个人感觉上理解起来稍容易一点:

第一种:

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)

你可能感兴趣的:(Python,算法,python,模拟退火算法,numpy)