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_first
和swap_last
的范围来限制可交换城市的范围
for swap_first in range(1,len(route)-3):
for swap_last in range(swap_first+1,len(route)-1):
要使起始城市和结束城市都可变,请将swap_first
和swap_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 中的旅行推销员的主要内容,如果未能解决你的问题,请参考以下文章