我有一个大约100个节点和大约200个边的无向图。一个节点标记为“开始”,一个节点标记为“结束”,大约有十二个标记为“必须通过”。
我需要找到通过此图的最短路径,该路径以“开始”开始,以“结束”结束,并通过所有“必须通过”节点(以任何顺序)。
(http://3e.org/local/maize-graph.png / http://3e.org/local/maize-graph.dot.txt是有问题的图形-它表示宾夕法尼亚州兰开斯特的玉米迷宫)
我有一个大约100个节点和大约200个边的无向图。一个节点标记为“开始”,一个节点标记为“结束”,大约有十二个标记为“必须通过”。
我需要找到通过此图的最短路径,该路径以“开始”开始,以“结束”结束,并通过所有“必须通过”节点(以任何顺序)。
(http://3e.org/local/maize-graph.png / http://3e.org/local/maize-graph.dot.txt是有问题的图形-它表示宾夕法尼亚州兰开斯特的玉米迷宫)
Answers:
其他将此与“旅行推销员问题”相比较的人,可能还没有仔细阅读您的问题。在TSP中,目标是找到访问所有顶点的最短周期(哈密顿周期)-它对应于将每个节点标记为“必须通过”。
在您的情况下,假设您只有大约十二个标记为“必须通过”的标签,并且有十二个!相当小(479001600),您可以仅尝试“必须通过”节点的所有排列,并查看从“开始”到“结束”的最短路径,以该顺序访问“必须通过”节点-这将简单地是该列表中每两个连续节点之间的最短路径的串联。
换句话说,首先要找到每对顶点之间的最短距离(您可以使用Dijkstra的算法或其他方法,但是只有很少的数目(100个节点),即使是最简单的代码Floyd-Warshall算法也会及时运行)。然后,将其放在表中后,尝试“必须通过”节点的所有排列,然后进行其余排列。
像这样:
//Precomputation: Find all pairs shortest paths, e.g. using Floyd-Warshall
n = number of nodes
for i=1 to n: for j=1 to n: d[i][j]=INF
for k=1 to n:
for i=1 to n:
for j=1 to n:
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
//That *really* gives the shortest distance between every pair of nodes! :-)
//Now try all permutations
shortest = INF
for each permutation a[1],a[2],...a[k] of the 'mustpass' nodes:
shortest = min(shortest, d['start'][a[1]]+d[a[1]][a[2]]+...+d[a[k]]['end'])
print shortest
(当然,这不是真正的代码,如果您想要实际的路径,则必须跟踪哪个排列给出最短的距离以及所有对最短的路径是什么,但是您知道了。)
它可以在任何合理的语言下最多运行几秒钟:)
[如果您有n个节点和k个“必须通过”节点,则对于Floyd-Warshall部分,其运行时间为O(n 3),而O(k!n )的所有排列部分,除非您有一些严格的限制条件,否则100 ^ 3 +(12!)(100)实际上是花生。]
a
到c
过b
。b
到a
和c
共享一条边的最短路径可能是。在这种情况下,边缘将重复两次。为了不产生周期,两条路径之一必须比最优路径更差。
INF
,因此该行d[i][j] = min(d[i][j], d[i][k] + d[k][j])
变为,d[i][j] = min(INF, INF + INF)
并且所有单元格始终等于INF
。您需要添加一个步骤,以图形中的边长填充此数组。
运行Djikstra的算法来查找所有关键节点(开始,结束和必须通过)之间的最短路径,然后深度优先遍历应该告诉您通过接触所有节点start ..的子图的最短路径。必须通过...结束
实际上,您发布的问题与旅行商相似,但我认为更接近一个简单的寻路问题。无需访问每个节点,您只需要在尽可能短的时间(距离)内访问一组特定的节点即可。
原因是,与旅行商问题不同,玉米迷宫不允许您直接从地图上的任何一个点到其他任何点旅行,而无需经过其他节点才能到达那里。
我实际上建议将A *寻路技术考虑在内。通过确定哪些节点可以直接访问哪些其他节点以及来自特定节点的每个跃点的“开销”是多少来进行设置。在这种情况下,似乎每个“跳”的成本都相同,因为您的节点之间的间隔似乎相对较近。A *可以使用此信息来找到任意两点之间的最低成本路径。由于您需要从A点到达B点并在大约12点之间进行访问,因此即使是使用寻路的暴力手段也完全不会受到伤害。
只是要考虑的替代方法。它看起来非常像旅行商问题,这些都是很好的试卷上阅读,但仔细看,你会看到它的唯一的事情过于复杂。^ _ ^这是从以前处理过这类事情的视频游戏程序员的脑海中得出的。
安德鲁·托普(Andrew Top)有一个正确的主意:
1)Djikstra的算法2)一些TSP启发式。
我建议使用Lin-Kernighan启发式方法:它是任何NP完全问题中最著名的方法之一。唯一需要记住的另一件事是,在第2步之后再次扩展图形之后,您的扩展路径中可能会有回路,因此您应该绕开这些回路(看看沿路径的顶点度)。
我实际上不确定这种解决方案相对于最佳解决方案的效果如何。短路可能与某些病理情况有关。毕竟,这个问题看起来像很多Steiner Tree:http://en.wikipedia.org/wiki/Steiner_tree,例如,仅通过收缩图形并运行Kruskal,您肯定无法近似Steiner Tree。
这不是TSP问题,也不是NP难题,因为原始问题不要求必须通过的节点仅被访问一次。通过Dijkstra的算法,在所有必须通过的节点之间的最短路径列表汇总之后,这使答案变得非常简单,只需蛮力。可能会有更好的方法,但是一个简单的方法就是简单地向后工作二叉树。想象一个节点列表[开始,a,b,c,结束]。求和简单距离[开始-> a-> b-> c->结束],这是您要拍的新目标距离。现在尝试[start-> a-> c-> b-> end],如果最好将其设置为目标(请记住,它来自该节点模式)。向后处理排列:
其中之一将最短。
(“多次访问”的节点在哪里(如果有)?它们只是隐藏在最短路径初始化步骤中。a和b之间的最短路径可能包含c甚至是端点。您无需关心)
考虑到节点和边的数量相对有限,您可能可以计算出所有可能的路径并采用最短的路径。
通常,这称为旅行商问题,无论您使用哪种算法,都具有不确定的多项式运行时间。
问题以ANY顺序谈论必须通过。我一直在尝试搜索有关必须通过的节点的已定义顺序的解决方案。我找到了答案,但由于关于StackOverflow的任何问题都没有类似的问题,因此我在此发布,以使最大的人受益。
如果定义了顺序或必须通过,则可以多次运行dijkstra的算法。例如,假设您必须从s
pass through开始k1
,k2
然后k3
(按各自的顺序)在处停止e
。然后,您可以做的是在每对连续的节点对之间运行dijkstra的算法。在成本和路径由下式给出:
dijkstras(s, k1) + dijkstras(k1, k2) + dijkstras(k2, k3) + dijkstras(k3, 3)
如何在十二个“必访”节点上使用蛮力。您可以很容易地覆盖12个节点的所有可能组合,这为您提供了一个最佳电路,可以用来覆盖它们。
现在,您的问题已简化为找到从起始节点到电路的最佳路线之一,然后围绕该路线直至覆盖所有路线,然后找到从该终点到终点的路线。
最终路径包括:
开始->电路路径*->必须访问节点的电路->结束路径*->结束
您会发现我用*标记的路径是这样的
从起始节点到电路上的每个点进行A *搜索,对于每个节点,从电路的下一个和上一个节点到末端进行A *搜索(因为您可以沿任一方向跟踪电路)最终会有很多搜索路径,您可以选择成本最低的搜索路径。
通过缓存搜索还有很多优化的空间,但是我认为这将产生好的解决方案。
但是,它并没有在寻找最佳解决方案的任何地方,因为这可能涉及将必须访问电路留在搜索范围内。