蚁群算法解决TSP问题(附代码python)

一、问题描述

1.1旅行商问题简述

旅行商问题,即TSP问题(Travelling Salesman Problem)又译为旅行推销员问题、货郎担问题,是数学领域中著名问题之一。假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

1.2旅行商问题求解算法

本次实验选用蚁群算法来对旅行商问题求解。

二、实验目的

理解蚁群行为的蚁群优化算法的求解过程。

三、实验内容

3.1蚁群算法描述

蚁群算法最开始是由意大利学者通过观察蚂蚁觅食习性时发现的,蚂蚁总能找到蚁穴和食物之间的最短路径。因此,我们就想知道为什么蚁群从洞穴出发搬运食物,总能找到一条蚁巢与食物之间的最优路径?
经研究发现,在自然界中,蚂蚁会分泌一种化学刺激物——信息素;蚂蚁在移动的过程中,能够在自己走过的地方留下信息素,且蚁群们能够感知这种物质的存在及其强度,并以此指导自己行走的方向,蚂蚁更倾向于向信息素浓度高的地方移动。
并且,在同等时间内蚁群在搬运食物的过程中,越短的路径上留下的信息素越多,则在后续的搬运中,最短路径上的蚂蚁越来越多。其实,这里的最短路径并非一定是路程最短路径,而是接近于全局的最优的“最短路径”。

3.2初始化数据

Step 1: 随机生成所有城市的坐标 (city_x, city_y)
Step 2: 计算任意两城市之间的距离 (distance) 和能见度 (eta)
Step 3: 用贪婪算法得出初始路径
Step 4: 计算得出并记录所有路径的信息素浓度(tau)

3.3蚁群算法步骤

Step 1: 第 t 次迭代
Step 2: 第 t 次迭代, 初始化: 所有蚂蚁 (m) 的路径 (path[m][]), 出发城市 (path[m][0]),允许去的城市 (allowed[m][]) 即本次迭代中未去过的城市
Step 3: 第 t 次迭代, 第 c 次选择下一个城市
Step 4: 第 t 次迭代, 第 c 次选择下一个城市, 第 m 只蚂蚁选择下一个城市
Step 5: 第 t, c, m 中, 初始化:估值函数 pij[0][],
估值的概率占比 pij[1][],
比例选择时的概率点 pij[2][]
Step 6: 第 t, c, m 中, 获得蚂蚁 m 的允许去的城市 cho = []
Step 7: 第 t, c, m 中, 计算蚂蚁 m 去城市 n 的 估值函数
Step 8: 第 t, c, m 中, 计算蚂蚁 m 去城市 n 的 估值函数值 在所有估值函数值之和的占比
Step 9: 第 t, c, m 中, 计算比例选择 (轮盘赌) 时, 去城市 n 还是城市 n+1 的概率选择点
Step 10: 第 t, c, m 中, 模仿轮盘赌, 随机比例选择
Step 11: 第 t 次迭代, 所有蚂蚁回到出发城市, 形成一条首尾相连的路径
Step 12: 第 t 次迭代, 计算所有蚂蚁 (m) 的路径长度 (mplen[m]), 途经城市 (taumnn[m][][])
Step 13: 第 t 次迭代, 更新路径 (tau[i][j]) 的信息素 (蒸发剩下的 + 新留下的)
Step 14: 第 t 次迭代, 选出所有蚂蚁 (m) 路径长度 (mplen[m]) 中的最短路径长度
Step 15: 第 t 次迭代, 判断是否出现比全局最短路径更短的本次迭代最短路径, 并更新

四、实验结果及分析

4.1实验结果

(1)打印出全局最短路径、长度、及最短路径迭代出现的次数。
在这里插入图片描述

(2)画出路径图
在这里插入图片描述

(3)画出迭代次数与最短路径的关系图
在这里插入图片描述

4.2分析

在给定的n个城市,从指定起点城市出发,访问依次每个城市一次。蚂蚁在运动过程中,根据各条路径上的信息素和启发信息按概率决定转移方向。可以根据如下公式,进行寻求最优路径。
在这里插入图片描述

各路径信息素消散规则及蚁群信息素更新规则:
在这里插入图片描述

单只蚂蚁释放信息素的规律可以采用以下三种模型:
在这里插入图片描述

五、遇到的问题及解决办法

5.1蚁群算法存在的弊端

(1)收敛速度慢。蚁群算法中信息素初值相同,选择下一个节点时倾向于随机选择。虽然随机选择能探索更大的任务空间,有助于找到潜在的全局最优解,但是需要较长时间才能发挥正反馈的作用,导致算法初期收敛速度较慢。
(2)局部最优问题。蚁群算法具有正反馈的特点,初始时刻环境中的信息素完全相同,蚂蚁几乎按随机方式完成解的构建,这些解必然会存在优劣之分。在信息素更新时,蚁群算法在较优解经过的路径上留下更多的信息激素,而更多的信息激素又吸引了更多的蚂蚁,这个正反馈的过程迅速地扩大初始的差异,引导整个系统向最优解的方向进化。虽然正反馈使算法具有较好的收敛速度,但是如果算法开始得到的较优解为次优解,那么正反馈会使次优解很快占据优势,使算法陷入局部最优,且难以跳出局部最优。
(3)优化能力问题。蚁群算法中参数众多并且具有一定的关联性,虽然蚁群算法在很多领域都有广泛应用,但是参数选择更多是依赖经验和试错,不恰当的初始参数会减弱算法的寻优能力。当进行路径规划时,为避免形成环形路径或者重复访问某些节点在算法中设置禁忌表,但是禁忌表很容易造成“死锁”现象,减少种群中的有效蚂蚁数量,降低算法的优化效率。
(4)种群多样性与收敛速度的矛盾。种群多样性对应于候选解在问题空间的分布。个体分布越均匀,种群多样性就越好,得到全局最优解的概率就越大,但是寻优时间就越长;个体分布越集中,种群多样性就越差,不利于发挥算法的探索能力。正反馈加快了蚁群算法的收敛速度,却使算法较早地集中于部分候选解,因此正反馈降低了种群的多样性,也不利于提高算法的全局寻优能力。

5.2解决办法

1.蚁群算法的结构改进
2.蚁群算法的参数优化
3.信息素初始化方法的改进
4.信息素更新规则的改进

全部代码

# -*- coding: utf-8 -*-
import math
import random
import copy
import matplotlib.pyplot as plt

# 地图长度
L = 100
# 地图高度
H = 80
# 城市个数
N = 20
# 蚂蚁个数
M = 20
# 迭代次数
T = 300

# 地图的对角线长度
LH = int(math.sqrt(L * L + H * H))

# 信息素的加权值
alpha = 1
# 能见度的加权值
beta = 2
# 信息素的蒸发率
rho = 0.5

# 城市的横坐标
city_x = [0 for n in range(N)]
# 城市的纵坐标
city_y = [0 for n in range(N)]

# 城市i和城市j之间的距离
distance = [[0 for j in range(N)] for i in range(N)]
# 能见度, 两点之间距离的倒数, 启发信息函数
eta = [[0 for j in range(N)] for i in range(N)]

# 当前时刻, 城市i和城市j之间的道路上信息素的值
tau = [[0 for j in range(N)] for i in range(N)]

# pathlen[t] 第 t 次迭代后得出的路径长度
pathLen = []
# pathCity[t] 第 t 次迭代后得出的路径
pathCity = []

# 第 best[0] 次迭代的路径最短, 全局最短路径的编号
best = [0]
# 全局最短路径上依次经过的城市的横坐标
X = []
# 全局最短路径上依次经过的城市的纵坐标
Y = []


# 初始化
# Step 1: 随机生成所有城市的坐标 (city_x, city_y)
# Step 2: 计算任意两城市之间的距离 (distance) 和能见度 (eta)
# Step 3: 用贪婪算法得出初始路径
# Step 4: 计算得出并记录所有路径的信息素浓度(tau)

def init():
    # ------------------------------------------- Step 1
    # 遍历所有城市
    for n in range(N):
        # 随机横坐标
        x = random.randint(0, L - 1)
        # 随机纵坐标
        y = random.randint(0, H - 1)
        # 记录城市 n 的横坐标
        city_x[n] = x
        # 记录城市 n 的纵坐标
        city_y[n] = y
    # ------------------------------------------- Step 2
    # 从城市 i 出发
    for i in range(N):
        # 到达城市 j
        for j in range(N):
            # 城市 i 和城市 j 之间的距离
            dij = math.pow(city_x[i] - city_x[j], 2)
            dij = dij + math.pow(city_y[i] - city_y[j], 2)
            dij = math.sqrt(dij)
            # 记录两城市之间的距离
            distance[i][j] = dij
            # 计算能见度
            # 如果 i 等于 j
            if i == j:
                # 城市到自己的能见度为 0
                eta[i][j] = 0
            else:
                # 两城市之间的能见度为两城市之间距离的倒数
                eta[i][j] = 1 / dij
    # ------------------------------------------- Step 3
    # 允许去的城市, 即未去过的城市
    # 0: 不允许, 1: 允许
    allow = [1 for n in range(N)]
    # 假设:从城市0出发
    allow[0] = 0
    # 路径
    apath = [0]
    # 当前位置
    apos = 0
    # 下一步去的城市
    acity = 0
    # 与下一个城市的距离
    away = 0
    # 总路径长度
    alen = 0
    #  第 c 次去往下一个城市
    for c in range(N - 1):
        # 设置去往下一个城市的距离, 最大值
        away = LH
        # 选择去往哪一个城市
        for n in range(N):
            # 如果允许去城市 n
            if allow[n] == 1:
                # 如果去城市 n 的距离 小于 当前要去的城市的距离
                if distance[apos][n] < away:
                    # 更新要去的城市
                    acity = n
                    # 更新要去的城市的距离
                    away = distance[apos][n]
        # 更新所在的位置
        apos = acity
        # 更新路径
        apath.append(apos)
        # 更新总路径长度
        alen = alen + away
        # 更新允许去的城市
        allow[apos] = 0
    # 回到出发点
    apath.append(0)
    # 更新总路径长度
    alen = alen + distance[apos][0]
    # 添加初始路径长度
    pathLen.append(alen)
    # 添加初始路径经过的城市
    pathCity.append(copy.deepcopy(apath))
    # ------------------------------------------- Step 4
    # 获得信息素的初始浓度值
    tau0 = M / alen
    # 设置所有城市之间路径的信息素浓度
    for i in range(N):
        for j in range(N):
            tau[i][j] = tau0
    # 城市到本身无路径, 信息素浓度为 0
    for n in range(N):
        tau[n][n] = 0


# 多次迭代
# Step  1: 第 t 次迭代
# Step  2: 第 t 次迭代, 初始化: 所有蚂蚁 (m) 的路径 (path[m][]), 出发城市 (path[m][0]),
#                              允许去的城市 (allowed[m][]) 即本次迭代中未去过的城市
# Step  3: 第 t 次迭代, 第 c 次选择下一个城市
# Step  4: 第 t 次迭代, 第 c 次选择下一个城市, 第 m 只蚂蚁选择下一个城市
# Step  5: 第 t, c, m 中, 初始化:估值函数          pij[0][],
#                               估值的概率占比     pij[1][],
#                               比例选择时的概率点 pij[2][]
# Step  6: 第 t, c, m 中, 获得蚂蚁 m 的允许去的城市 cho = []
# Step  7: 第 t, c, m 中, 计算蚂蚁 m 去城市 n 的 估值函数
# Step  8: 第 t, c, m 中, 计算蚂蚁 m 去城市 n 的 估值函数值 在所有估值函数值之和的占比
# Step  9: 第 t, c, m 中, 计算比例选择 (轮盘赌) 时, 去城市 n 还是城市 n+1 的概率选择点
# Step 10: 第 t, c, m 中, 模仿轮盘赌, 随机比例选择
# Step 11: 第 t 次迭代, 所有蚂蚁回到出发城市, 形成一条首尾相连的路径
# Step 12: 第 t 次迭代, 计算所有蚂蚁 (m) 的路径长度 (mplen[m]), 途经城市 (taumnn[m][][])
# Step 13: 第 t 次迭代, 更新路径 (tau[i][j]) 的信息素 (蒸发剩下的 + 新留下的)
# Step 14: 第 t 次迭代, 选出所有蚂蚁 (m) 路径长度 (mplen[m]) 中的最短路径长度
# Step 15: 第 t 次迭代, 判断是否出现比全局最短路径更短的本次迭代最短路径, 并更新

def iteration():
    # ------------------------------------------- Step 1
    # 第 t 次迭代
    for t in range(1, T + 1):
        # --------------------------------------- Step 2
        # 所有蚂蚁的路径
        path = [[] for m in range(M)]
        # 蚂蚁 m 从 城市 m 出发
        for m in range(M):
            path[m].append(m)
        # 蚂蚁允许去的城市
        allowed = [[0 if i == j else 1 for j in range(N)] for i in range(N)]
        # 第 c 次去往下一个城市, 除了出发城市, 有 N-1 个城市
        # --------------------------------------- Step 3
        for c in range(N - 1):
            # ----------------------------------- Step 4
            # 第 m 只蚂蚁选择下一个城市
            for m in range(M):
                # ------------------------------- Step 5
                # 估值函数, 估值的概率占比, 比例选择时的概率点
                pij = [[0 for j in range(N)] for i in range(3)]
                # 去往的下一个城市的编号
                city = 0
                # 在第 c 次选择城市时, 可选城市的编号
                # ------------------------------- Step 6
                cho = []
                # 判断城市 n 是否可选
                for n in range(N):
                    # 如果可选
                    if allowed[m][n] == 1:
                        # 添加入 cho
                        cho.append(n)
                # ------------------------------- step 7
                # 遍历所有可去城市
                for n in cho:
                    # 蚂蚁 m 所处的当前城市
                    x = path[m][-1]
                    # 蚂蚁 m 下一步去从城市 n 的概率
                    pij[0][n] = math.pow(tau[x][n], alpha)
                    pij[0][n] = pij[0][n] * math.pow(eta[x][n], beta)
                # -------------------------------step 8
                # 求和
                p1 = sum(pij[0])
                # 归一化
                for n in cho:
                    # 蚂蚁 m 去从城市 n 的概率 占 所有概率之和的比例
                    pij[1][n] = pij[0][n] / p1
                # ------------------------------- Step 9
                # 比例选择法(轮盘赌法)的第一个概率点
                p2 = 0
                # 遍历所有可去城市
                for n in cho:
                    # 获得所有概率点
                    pij[2][n] = p2 + pij[1][n]
                    p2 = pij[2][n]
                # ------------------------------- Step 10
                # 模仿轮盘, 随机选择
                rand = random.random()
                # 遍历所有可去城市
                for n in cho:
                    # 如果概率点落在去城市 n 的扇面内
                    if pij[2][n] > rand:
                        # 则去城市 n
                        city = n
                        # 结束遍历
                        break
                # 更新路径
                path[m].append(city)
                # 更新允许去的城市
                allowed[m][city] = 0
        # --------------------------------------- Step 11
        # 回到出发城市
        for m in range(M):
            # 添加路径
            path[m].append(m)
        # --------------------------------------- Step 12
        # 所有蚂蚁走完所有城市的路径长度
        mplen = []
        # 蚂蚁 m 是否经过城市 i 到城市 j 的路径, 留下信息素
        taumnn = []
        # 遍历所有蚂蚁
        for m in range(M):
            # 初始设置: 蚂蚁 m 没有经过城市 i 到城市 j 的路径
            taunn = [[0 for i in range(N)] for j in range(N)]
            # 总路径长度为 0
            plen = 0
            # 遍历蚂蚁 m 经过的城市
            for p in range(N):
                # 出发城市
                x = path[m][p]
                # 到达城市
                y = path[m][p + 1]
                # 更新路径长度
                plen = plen + distance[x][y]
                # 在城市 x 到城市 y 的路径上留下信息素
                taunn[x][y] = 1
            # 更新所有蚂蚁的路径总长度的列表
            mplen.append(plen)
            # 更新所有蚂蚁留下信息素的路径列表
            taumnn.append(copy.deepcopy(taunn))
        # --------------------------------------- Step 13
        # 城市 i 出发
        for i in range(N):
            # 到达城市 j
            for j in range(N):
                # 蚂蚁留下的信息素
                taumij = 0
                # 遍历所有蚂蚁留下信息素的路径
                for m in range(M):
                    # 如果蚂蚁 m 在城市 i 到城市 j 的路径上留下信息素
                    if taumnn[m][i][j] == 1:
                        # 更新该段路径留下的的信息素之和
                        taumij = taumij + 1 / mplen[m]
                # 更新该路径的信息素(蒸发剩下的 + 新留下的)
                tau[i][j] = (1 - rho) * tau[i][j] + taumij
        # --------------------------------------- Step 14
        # 路径总长度的最大值 小于 对角线长度的城市个数倍
        pathlent = LH * N
        # 蚂蚁 ant 在本次迭代中走的路径最短
        ant = 0
        # 遍历所有蚂蚁
        for m in range(M):
            # 如果蚂蚁 m 走的路径长度 小于 本次迭代的最短路径
            if mplen[m] < pathlent:
                # 更新最短路径
                pathlent = mplen[m]
                # 更新蚂蚁编号
                ant = m
        # 添加本次迭代的最短路径
        pathLen.append(pathlent)
        # 添加本次迭代的最短路径经过的城市
        pathCity.append(copy.deepcopy(path[ant]))
        # --------------------------------------- Step 15
        # 判断是否出现比全局最短路径更短的本次迭代最短路径
        if pathlent < pathLen[best[0]]:
            # 更新全局最短路径的编号
            best[0] = len(pathLen)
# 展示结果
# Step 1: 输出 每次迭代的最短路径长度
# Step 2: 输出 全局最短路径长度 及其 首次出现 的 迭代次数
# Step 3: 可视化展示 所有城市 的 位置
# Step 4: 可视化展示 全局最短路径
# Step 5: 可视化展示 每次迭代的最短路径长度 的变化趋势

def showResult():
    # ------------------------------------------- Step 1
    # 遍历所有的迭代结果
    for t in range(T + 1):
        # 输出 t 次迭代的最短路径长度
        print("{0:>3} {1:>16}".format(t, pathLen[t]))
    # ------------------------------------------- Step 2
    # 输出全局最短路径及其长度, 首次出现的迭代次数
    print("\n全局最短路径: ", pathCity[best[0]], " 长度: ", pathLen[best[0]])
    print("首次出现在第 ", best[0], "次迭代")
    # ------------------------------------------- Step 3
    # 画布
    plt.figure(1)
    # 子图 1
    plt.subplot(1,1,1)
    # 散点图展示所有城市的位置
    plt.scatter(city_x, city_y, color='b')
    # 遍历所有城市
    for n in range(N):
        # 城市 n 的坐标信息
        msg = "({},{})".format(city_x[n], city_y[n])
        # 需标注的城市的坐标
        x = city_x[n]
        y = city_y[n]
        # 文本标注的位置
        xt = x + 0.5
        yt = y + 0.5
        # 标注
        plt.annotate(msg, xy=(x, y), xytext=(xt, yt))
    # ------------------------------------------- Step 4
    # 遍历全局最短路径经过的城市
    for p in pathCity[best[0]]:
        # 横坐标
        X.append(city_x[p])
        # 纵坐标
        Y.append(city_y[p])
    # 折线图展示: 全局最短路径
    plt.plot(X, Y, color='c')
    # x 轴标签
    plt.xlabel("city_x")
    # y 轴标签
    plt.ylabel("city_y")
    # 标题
    plt.title("Global shortest path")
    # 添加网格线
    plt.grid()
    # ------------------------------------------- Step 5
    # 子图 2
    plt.figure(2)
    plt.subplot(1,1,1)
    # 折线图展示: 每次迭代路径最短长度的变化趋势
    plt.plot([t for t in range(T + 1)], pathLen)
    # x 轴标签
    plt.xlabel("T iteration")
    # y 轴标签
    plt.ylabel("The shortest length")
    # 标题
    plt.title("Changing trend")
    # 展示输出
    plt.show()

# 初始化
init()
# 多次迭代
iteration()
# 展示结果
showResult()


评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辉在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值