旅行家(TSP)问题

最近做吉比特的笔试题,遇到了一道改版的旅行家(TSP)问题。然而我对正常的TSP都还没有特别了解,于是回过头来稍微研究一下。

问题描述

Travelling Salesman Problem(TSP)是最基本的路线问题,它寻求的是旅行者从起点出发,通过所有给定的需求点之后,再次返回起点所花费的最小路径成本,也叫旅行商问题、旅行推销员问题。

动态规划

先说说动态规划(Dynamic Programming),我们知道动态规划通常用于求解具有某种最优性质的问题,其基本思想在于将待求解问题分解成若干个子问题(状态),先求解子问题,再又这些子问题的解求得愿问题的解(状态转移)。

对于TSP问题,我们这里可以用一个具体的例子来描述:

状态压缩DP

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 尝试着自己写TSP,基于动态规划
# 假设0为起点,实际上从哪里出发不重要,最后的路径是一条回路,从哪里出发都一样
import sys
def tsp(roadmap):
n = len(roadmap)
dp = [[0x7fffff] * (2 ** (n - 1)) for _ in range(n)]
track = [[-1] * (2 ** (n - 1)) for _ in range(n)]

for row in range(n):
for col in range(n):
if roadmap[row][col] == -1:
roadmap[row][col] = 0x7fffff

# 更新第一列
for row in range(n):
dp[row][0] = roadmap[row][0]
track[row][0] = 0

# 更新后面的列
for col in range(1, 2 ** (n - 1)):
for row in range(n):
if row > 0 and (col >> (row - 1) & 1) == 1:
continue
for k in range(1, n):
if (col >> (k - 1) & 1) == 0:
continue

length = roadmap[row][k] + dp[k][col ^ (1 << (k - 1))]
if length < dp[row][col]:
dp[row][col] = length
track[row][col] = k
# dp[row][col] = min(dp[row][col], roadmap[row][k] + dp[k][col ^ (1 << (k - 1))])

# 回溯路径
path = [0]
i, j = 0, 2 ** (n - 1) - 1
while j != 0:
i = track[i][j]
j = j ^ (1 << (i - 1))
path.append(i)
path.append(0)

# 输出
return dp[0][-1], path

测试样例:

1
2
3
4
5
6
7
8
9
10
# D = [[-1,10,20,30,40,50],[12,-1,18,30,25,21],[23,19,-1,5,10,15],[34,32,4,-1,8,16],[45,27,11,10,-1,18],[56,22,16,20,12,-1]]
D=[
[-1, 3, 6, 7],
[ 5,-1, 2, 3],
[ 6, 4,-1, 2],
[ 3, 7, 5,-1]]
# D = [[-1, 1], [1, -1]]
r, path = tsp(D)
print(r)
print(" --> ".join([str(x) for x in path]))

输出:

1
2
10
0 --> 1 --> 2 --> 3 --> 0

参考

使用动态规划求解旅行商问题
用动态规划算法求解Travelling Salesman Problem(TSP)问题
hdu 3001 Travelling 旅行商问题(每个城市可以走两遍)