SciPy中的旅行商问题

13

如何在Python中解决旅行商问题?我没有找到任何库,但可以使用scipy函数进行优化或其他库来解决问题。

我的草率、极其懒惰的Python蛮力破解方案是:

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太大,这将需要很长时间。

有什么建议吗?


当您说解决问题时,是什么意思?对于非常大数量的城市寻找单一最短路线不是人类所知道的事情,除了详尽地检查组合的方案之外,这是非常困难的。那么接近最优解是否可行?我们能否将城市数量的限制设置为<=60? - BKay
@BKay 当然,我们确实做到了。早在2006年,我们就解决了一个包含85900个城市的实例。我向你保证,这不是通过蛮力法完成的。总的来说,我们遇到了麻烦,因为它是NP完全问题,但这并不意味着我们不能聪明地解决它。 - harold
我知道对于许多城市来说这是无法解决的。我只想要一个最先进的启发式解决方案。(或者针对较少城市的更智能的确定性方法) - Gioelelm
没错。我说话太快了。有一些相当不错的近似解法,速度很快,还有一些比暴力方法复杂但更快的方法。我真的只是想知道你想要什么。 - BKay
Lin-Kernighan启发式算法表现相当不错。如果您想要实现真正的最优解,可以考虑基于线性规划的求解器。 - harold
https://www.google.com/search?q=lin+kernighan+python - pv.
3个回答

28

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)))

2-opt旅行商问题近似解决方案

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

编辑:自定义起始和结束点

对于非圆形路径(以不同于起点的位置结束的路径),请编辑路径距离公式为

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

然后使用xx重新排列城市以进行绘图

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)):

1
如果只是改变路径距离,这个实现是否适用于非圆形路径?(例如,使城市遍历不必在路径起点结束) - Gioelelm
@Gioelelm 是的,确实如此。我已经添加了如何更改非圆形路径的“path_distance”的说明。对于非圆形路径,您可能还想自定义起点和终点,因此我添加了有关如何自定义这些内容的注释。请注意,我对“two_opt_swap”进行了轻微修改,以适应可变起始城市。 - cameronroytaylor
这对于非对称TSP问题有效吗?我尝试了一个非对称的path_distance函数,但我不确定2-opt在非对称情况下是否有效 :? - jjmontes
1
@jjmontes 看起来这对于非对称问题是有效的,但我不知道它与其他启发式方法相比如何。我很想知道它的表现如何。此外,我建议创建一个预计算距离表。它将大大提高对称和非对称情况下的性能。 - cameronroytaylor
不错的答案。我发现这个解决方案更具可扩展性。 - zabop
很棒,谢谢分享!即使您预先计算距离并将其存储在矩阵中,ortools解决方案是否仍然显着更快? - cameronroytaylor

0

0
Python中有一个名为NetworkX的库,专门用于解决网络问题(理论计算机科学和运筹学中的顶点覆盖优化问题)。在它的文档(参考/算法/近似和启发式算法)中,你可以找到一个例子(也许,相对于scipy来说,它对于这种问题类型可能更方便,包括最短路径问题、最小费用流问题等)。TSP(旅行商问题):
NP难的图形旅行商问题(GTSP)是指在一个无向边权重图中找到一条闭合的最小权重路径,使其访问每个顶点,该图不一定是完全图。
"""
==========================
Traveling Salesman Problem
==========================

This is an example of a drawing solution of the traveling salesman problem

The function used to produce the solution is `christofides <networkx.algorithms.approximation.traveling_salesman.christofides>`,
where given a set of nodes, it calculates the route of the nodes
that the traveler has to follow in order to minimize the total cost.
"""

import matplotlib.pyplot as plt
import networkx as nx
import networkx.algorithms.approximation as nx_app
import math

G = nx.random_geometric_graph(20, radius=0.4, seed=3)
pos = nx.get_node_attributes(G, "pos")

# Depot should be at (0,0)
pos[0] = (0.5, 0.5)

H = G.copy()


# Calculating the distances between the nodes as edge's weight.
for i in range(len(pos)):
    for j in range(i + 1, len(pos)):
        dist = math.hypot(pos[i][0] - pos[j][0], pos[i][1] - pos[j][1])
        dist = dist
        G.add_edge(i, j, weight=dist)

cycle = nx_app.christofides(G, weight="weight")
edge_list = list(nx.utils.pairwise(cycle))

# Draw closest edges on each node only
nx.draw_networkx_edges(H, pos, edge_color="blue", width=0.5)

# Draw the route
nx.draw_networkx(
    G,
    pos,
    with_labels=True,
    edgelist=edge_list,
    node_color = 'r',
    edge_color="red",
    node_size=200,
    width=3,
)

print("The route of the traveller is:", cycle)
plt.show()

注意:在networkX的文档中,有不同的算法可以解决你的问题。
表述:《覆盖旅行商问题的分支定界算法》。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接