scipy 中的旅行推销员

Posted

技术标签:

【中文标题】scipy 中的旅行推销员【英文标题】:Travelling Salesman in scipy 【发布时间】:2014-10-24 11:25:53 【问题描述】:

如何在 python 中解决旅行商问题?我没有找到任何库,应该有使用 scipy 函数进行优化的方法或其他库。

我的 hacky-extremelly-lazy-pythonic 暴力破解解决方案是:

tsp_solution = min( (sum( Dist[i] for i in izip(per, per[1:])), n, per) for n, per in enumerate(i for i in permutations(xrange(Dist.shape[0]), Dist.shape[0])) )[2]

其中 Dist (numpy.array) 是距离矩阵。 如果 Dist 太大,这将需要很长时间。

建议?

【问题讨论】:

当你说解决你的意思是什么?为大量城市找到一条最短的路线并不是人类真正知道如何做的事情,除非详尽地检查组合,这是非常困难的事情。一个近乎最优的解决方案可以吗?我们能否将城市数量限制为 @BKay 当然可以,早在 2006 年就解决了一个包含 85900 个城市的实例。我向你保证,这不是用蛮力发生的。一般来说,我们遇到麻烦是因为它是 NP 完全的,但这并不意味着我们不能聪明。 我知道很多城市都无法解决。我只想要一个最先进的启发式解决方案。 (或针对较少城市的更智能的确定性方法) 没错。我说得太快了。有一些非常好的近似解决方案,它们很快,有些复杂但比蛮力方法快得多。我真的只是想知道你想要什么。 Lin-Kernighan 启发式算法效果很好。如果您想获得实际的最优性,可以查看基于线性规划的求解器。 【参考方案1】:

scipy.optimize 函数的构造不能直接适应旅行商问题 (TSP)。对于一个简单的解决方案,我推荐 2-opt 算法,这是一种被广泛接受的用于解决 TSP 的算法,并且实现起来相对简单。这是我的算法实现:

import numpy as np

# Calculate the euclidian distance in n-space of the route r traversing cities c, ending at the path start.
path_distance = lambda r,c: np.sum([np.linalg.norm(c[r[p]]-c[r[p-1]]) for p in range(len(r))])
# Reverse the order of all elements from element i to element k in array r.
two_opt_swap = lambda r,i,k: np.concatenate((r[0:i],r[k:-len(r)+i-1:-1],r[k+1:len(r)]))

def two_opt(cities,improvement_threshold): # 2-opt Algorithm adapted from https://en.wikipedia.org/wiki/2-opt
    route = np.arange(cities.shape[0]) # Make an array of row numbers corresponding to cities.
    improvement_factor = 1 # Initialize the improvement factor.
    best_distance = path_distance(route,cities) # Calculate the distance of the initial path.
    while improvement_factor > improvement_threshold: # If the route is still improving, keep going!
        distance_to_beat = best_distance # Record the distance at the beginning of the loop.
        for swap_first in range(1,len(route)-2): # From each city except the first and last,
            for swap_last in range(swap_first+1,len(route)): # to each of the cities following,
                new_route = two_opt_swap(route,swap_first,swap_last) # try reversing the order of these cities
                new_distance = path_distance(new_route,cities) # and check the total distance with this modification.
                if new_distance < best_distance: # If the path distance is an improvement,
                    route = new_route # make this the accepted best route
                    best_distance = new_distance # and update the distance corresponding to this route.
        improvement_factor = 1 - best_distance/distance_to_beat # Calculate how much the route has improved.
    return route # When the route is no longer improving substantially, stop searching and return the route.

这里是一个正在使用的函数的例子:

# Create a matrix of cities, with each row being a location in 2-space (function works in n-dimensions).
cities = np.random.RandomState(42).rand(70,2)
# Find a good route with 2-opt ("route" gives the order in which to travel to each city by row number.)
route = two_opt(cities,0.001)

这里是图上显示的近似解路径:

import matplotlib.pyplot as plt
# Reorder the cities matrix by route order in a new matrix for plotting.
new_cities_order = np.concatenate((np.array([cities[route[i]] for i in range(len(route))]),np.array([cities[0]])))
# Plot the cities.
plt.scatter(cities[:,0],cities[:,1])
# Plot the path.
plt.plot(new_cities_order[:,0],new_cities_order[:,1])
plt.show()
# Print the route as row numbers and the total distance travelled by the path.
print("Route: " + str(route) + "\n\nDistance: " + str(path_distance(route,cities)))

如果算法的速度对您很重要,我建议预先计算距离并将它们存储在矩阵中。这大大减少了收敛时间。

编辑:自定义起点和终点

对于非圆形路径(终点与起点不同的路径),将路径距离公式编辑为

path_distance = lambda r,c: np.sum([np.linalg.norm(c[r[p+1]]-c[r[p]]) for p in range(len(r)-1)])

然后重新排列城市以供绘制使用

new_cities_order = np.array([cities[route[i]] for i in range(len(route))])

按照代码,起始城市固定为cities中的第一个城市,结束城市是可变的。

为了使结束城市成为cities中的最后一个城市,通过使用代码更改two_opt()swap_firstswap_last的范围来限制可交换城市的范围

for swap_first in range(1,len(route)-3):
    for swap_last in range(swap_first+1,len(route)-1):

要使起始城市和结束城市都可变,请将swap_firstswap_last 的范围改为

for swap_first in range(0,len(route)-2):
    for swap_last in range(swap_first+1,len(route)):

【讨论】:

这个实现对非循环路径是否有效,只需更改 path_distance? (例如,城市遍历不必在路径开始处结束) @Gioelelm 是的,确实如此。我已经添加了关于如何将 path_distance 更改为非圆形路径的说明。对于非圆形路径,您可能还想自定义起点和终点,因此我还添加了有关如何自定义它们的注释。请注意,我对two_opt_swap 做了一些修改,以适应可变的起始城市。 这对非对称 TSP 问题有效吗?我尝试了一个不对称的 path_distance 函数,但我真的不知道 2-opt 是否适用于不对称情况:? @jjmontes 似乎这对于非对称问题是有效的,尽管我不知道它与其他启发式方法相比如何。我很想知道它的表现如何。另外,我建议创建一个预先计算的距离表。它将在对称和非对称情况下显着提高性能。 不错的答案。我发现this solution 更具可扩展性。

以上是关于scipy 中的旅行推销员的主要内容,如果未能解决你的问题,请参考以下文章

具有约束和可选城市的类似旅行推销员的问题

优化旅行推销员算法(时间旅行者算法)

如何解决旅行推销员问题的起点和终点?

旅行推销员将贬值物品运送到不同的市场

有多个推销员的旅行推销员,每个推销员的城市数量有限制?

遗传算法 - 旅行推销员