运筹规划(八)-TSP问题

咱们先来说下TSP问题的背景, 背景讲的是一个游客和几个景点的故事, 一个游客想用最小的代价游历完所有的景点,一个是方案怎么选择,一个是最小代价是什么?这就引出了TSP问题,这个问题在现实中的使用场景也比较多,例如典型的外卖配送场景,外卖员需要配送完所有的订单,最小的代价是什么?如何选择配送的顺序?结下来我们就来讲讲解决这个问题的常用做法。首先明确一点,TSP问题是一个NP难问题,大规模的精确求解是不太可能的。下面就是枚举了常见的做法。

Greed求解TSP问题

贪心算法算法十分简单,我们这里简单介绍思路即可。

Greed核心思路

从起点开始找到最近的点,然后走这条路径,更新访问后的集合,记住新的距离,依次类推后持续的持续通过这个方式选点并更新距离和路径集合。

DP求解TSP问题

import sys
def tsp_dynamic_programming(dist_matrix):
    n = len(dist_matrix)
    all_sets = 2 ** n  # 总共的子集数

    # dp[i][j]表示从起点出发经过子集j中所有城市,并以城市i结束的最短路径长度
    dp = [[float('inf')] * all_sets for _ in range(n)]

    # 初始化起点到每个城市的距离
    for i in range(n):
        dp[i][1 << i] = dist_matrix[i][0]

    for subset in range(1, all_sets):
        for end in range(1, n):
            if (subset >> end) & 1:  # 如果当前子集中包含城市end,可达
                for prev_end in range(n):
                    if (subset >> prev_end) & 1 and prev_end != end:
                        # 通过dp状态转移更新最短路径
                        dp[end][subset] = min(dp[end][subset],
                                              dp[prev_end][subset ^ (1 << end)] + dist_matrix[end][prev_end])

    # 找到起点到所有城市的最短路径
    result = float('inf')
    for end in range(1, n):
        result = min(result, dp[end][all_sets - 1] + dist_matrix[0][end])
    return result


if __name__ == "__main__":
    # 示例距离矩阵,假设有4个城市,起点为0,城市之间的距离表示为二维矩阵
    # 注意:对于无法直接到达的城市对之间的距离,可以设为一个很大的数或者inf表示不可达。
    distance_matrix = [
        [0, 10, 15, 20],
        [10, 0, 35, 25],
        [15, 35, 0, 30],
        [20, 25, 30, 0]
    ]
    shortest_path_length = tsp_dynamic_programming(distance_matrix)
    print("最短路径长度为:", shortest_path_length)

这里是实现看起来有一点复杂,其实就是用位的方式标记经过城市的范围。
dp[i][j]表示从i出发,经过j的二进制表达的城市集合,最小的距离是多少。
dp[3][10],其中10的二进制表示为‘1010’,表示从3城市出发经过0城市和2城市的最短距离是多少。很明显是35,首先从3城市出发去0城市20,从0城市出发去2城市15,最好就是15+20=35.

运筹求解TSP问题

当然TSP问题也可以使用运筹的方式解决。使用如下的方式建模。
n:城市的数量,假设有n个城市,编号为1到n。
d[i][j]:表示城市i到城市j之间的距离或成本(距离矩阵或成本矩阵)。
x[i][j]:二进制变量,表示是否从城市i直接到城市j(1表示经过,0表示不经过)。

minimizeΣi=1nΣj=1nd[i][j]x[i][j]s.t.Σj=1nx[i][j]=1,Σi=1nx[i][j]=1,x[i][j]+x[j][i]1,x[i][i]=0minimize Σ_{i=1}^{n} Σ_{j=1}^{n} d[i][j] * x[i][j] \\ s.t. \\ Σ_{j=1}^{n} x[i][j] = 1, \\ Σ_{i=1}^{n} x[i][j] = 1, \\ x[i][j] + x[j][i] ≤ 1,\\ x[i][i] = 0 \\

约束的含义依次是

  • 每个城市只能被访问一次
  • 每个城市只能离开一次
  • 子集约束
  • 排除回路

其他启发算法

因为TSP是一个NP难问题,所以当规模较大的求解的时候就需要一些启发算法,下面的算法是一个选择。

贪心算法(Greedy Algorithm)

贪心算法是一种简单而直观的启发式算法。它从一个起点城市开始,然后每次选择最近的未访问过的城市作为下一个访问城市,直到所有城市都被访问过,最后回到起点城市形成闭环。这种方法容易实现,但不保证得到最优解,因为它只考虑了局部最优选择。

最近邻居算法(Nearest Neighbor Algorithm)

最近邻居算法类似于贪心算法,但它每次选择最近的未访问过的城市作为下一个访问城市,并使用两种策略:最近邻居法和最远邻居法。最近邻居法选择与当前城市距离最近的未访问城市,而最远邻居法选择与当前城市距离最远的未访问城市。虽然最近邻居算法相对简单,但通常可以得到较好的近似解。

模拟退火算法(Simulated Annealing Algorithm)

模拟退火算法受到金属退火的物理过程启发。它以一定概率接受比当前解更差的解,并随着时间逐渐减小接受较差解的概率,从而逐步接近最优解。模拟退火算法可以避免陷入局部最优解,是一种全局优化方法,但需要合理设置参数和退火策略。

遗传算法(Genetic Algorithm)

遗传算法模拟了生物进化的过程,通过基因交叉和变异操作来搜索解空间。它通过选择和交叉优秀的解,以及引入一定的随机性,逐代优化解的质量。遗传算法适用于大规模问题,但需要适当的参数设置和操作策略。

粒子群优化算法(Particle Swarm Optimization, PSO)

PSO是受到鸟群觅食行为启发的优化算法,每个“粒子”代表一个可能的解,通过个体最优和群体最优来引导搜索过程。粒子根据自身经验和群体经验来更新自己的位置,并逐步优化解。PSO算法可以在全局和局部搜索之间取得平衡,是一种高效的优化方法。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×