在图中访问某些节点的最短路径


82

我有一个大约100个节点和大约200个边的无向图。一个节点标记为“开始”,一个节点标记为“结束”,大约有十二个标记为“必须通过”。

我需要找到通过此图的最短路径,该路径以“开始”开始,以“结束”结束,并通过所有“必须通过”节点(以任何顺序)。

http://3e.org/local/maize-graph.png / http://3e.org/local/maize-graph.dot.txt是有问题的图形-它表示宾夕法尼亚州兰开斯特的玉米迷宫)

Answers:


77

其他将此与“旅行推销员问题”相比较的人,可能还没有仔细阅读您的问题。在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)实际上是花生。]


5
鉴于输入量很小,我同意答案。但是我对为什么您拒绝其他人认为这是TSP的说法感兴趣。以我的理解,您可以提取所有必须通过的节点并使用它来创建子图。子图中节点之间的边缘的权重对应于原始图中的APSP解决方案。然后问题不成为子图上TSP的实例吗?您的解决方案似乎是问题的蛮力解决方案(很好)。
maditya

6
@maditya:首先,我希望你同意(引用史蒂文·阿洛的评论),诸如“ TSP很难,bhahahaha”之类的答案对于有真正问题要解决的人(尤其是非常容易解决的人)不是适当的答案。解决了过去几十年来在任何计算机上的问题。其次,由于琐碎的原因(不同的输入格式),这与TSP并不相同:它包含的TSP微小实例用于较小的图,而不是输入大小N之一。因此,NP完整性取决于多少个“必须通过”节点有渐近:如果它总是12,或者为O(log N),它不是NP完全等
ShreevatsaR

1
我不确定结果是否是路径。想象一下,从去acbbac共享一条边的最短路径可能是。在这种情况下,边缘将重复两次。为了不产生周期,两条路径之一必须比最优路径更差。
Spak

1
@PietroSaccardi从问题的描述看来,目标似乎只是找到穿过所有这些节点的最短“路线”,并且如果重复某些边缘,则可以。也就是说,“路径”在广义上被使用。实际上,如果不允许重复的边,甚至可能没有答案(例如,考虑图B—A—C,其中要求您经过B时从A转到C:无法重复) B-A边)。
ShreevatsaR

Floyd-Warshall算法在此处未正确实现:由于数组的所有单元格都用初始化INF,因此该行d[i][j] = min(d[i][j], d[i][k] + d[k][j])变为,d[i][j] = min(INF, INF + INF)并且所有单元格始终等于INF。您需要添加一个步骤,以图形中的边长填充此数组。
Stef

24

运行Djikstra的算法来查找所有关键节点(开始,结束和必须通过)之间的最短路径,然后深度优先遍历应该告诉您通过接触所有节点start ..的子图的最短路径。必须通过...结束


这似乎比选择的解决方案更有效率。我敢打赌,如果您再充实一点,它的得分会更高。我首先要考虑所有对之间的最短路径是否可以保证所有必需节点之间的路径。但是我相信它会(假设是无向的)。
galactikuh

我认为如果路径一定不能重复边缘,这是行不通的。
tuket

1
在代码出现第24天的示例中,深度优先遍历如何找到最短路径?adventofcode.com/2016/day/24
Erwin Rooijakkers '02

15

这是两个问题...史蒂文·洛(Steven Lowe)指出了这一点,但没有对问题的后半部分给予足够的重视。

首先,您应该发现所有关键节点(起点,终点,必须通过)之间的最短路径。一旦发现这些路径,您就可以构造一个简化的图形,其中新图形中的每个边都是从一个关键节点到原始图形中另一个关键节点的路径。您可以使用许多寻路算法在此处找到最短路径。

但是,一旦有了这个新图表,您就会遇到旅行销售员问题(嗯,几乎……无需回到起点)。上述任何与此相关的职位都将适用。


14

实际上,您发布的问题与旅行商相似,但我认为更接近一个简单的寻路问题。无需访问每个节点,您只需要在尽可能短的时间(距离)内访问一组特定的节点即可。

原因是,与旅行商问题不同,玉米迷宫不允许您直接从地图上的任何一个点到其他任何点旅行,而无需经过其他节点才能到达那里。

我实际上建议将A *寻路技术考虑在内。通过确定哪些节点可以直接访问哪些其他节点以及来自特定节点的每个跃点的“开销”是多少来进行设置。在这种情况下,似乎每个“跳”的成本都相同,因为您的节点之间的间隔似乎相对较近。A *可以使用此信息来找到任意两点之间的最低成本路径。由于您需要从A点到达B点并在大约12点之间进行访问,因此即使是使用寻路的暴力手段也完全不会受到伤害。

只是要考虑的替代方法。它看起来非常像旅行商问题,这些都是很好的试卷上阅读,但仔细看,你会看到它的唯一的事情过于复杂。^ _ ^这是从以前处理过这类事情的视频游戏程序员的脑海中得出的。


2
+1-比“旅行推销员的问题很难解决,bhahahaha”要好得多
史蒂文·A·洛

5

安德鲁·托普(Andrew Top)有一个正确的主意:

1)Djikstra的算法2)一些TSP启发式。

我建议使用Lin-Kernighan启发式方法:它是任何NP完全问题中最著名的方法之一。唯一需要记住的另一件事是,在第2步之后再次扩展图形之后,您的扩展路径中可能会有回路,因此您应该绕开这些回路(看看沿路径的顶点度)。

我实际上不确定这种解决方案相对于最佳解决方案的效果如何。短路可能与某些病理情况有关。毕竟,这个问题看起来像很多Steiner Tree:http//en.wikipedia.org/wiki/Steiner_tree,例如,仅通过收缩图形并运行Kruskal,您肯定无法近似Steiner Tree。


5

不是TSP问题,也不是NP难题,因为原始问题不要求必须通过的节点仅被访问一次。通过Dijkstra的算法,在所有必须通过的节点之间的最短路径列表汇总之后,这使答案变得非常简单,只需蛮力。可能会有更好的方法,但是一个简单的方法就是简单地向后工作二叉树。想象一个节点列表[开始,a,b,c,结束]。求和简单距离[开始-> a-> b-> c->结束],这是您要拍的新目标距离。现在尝试[start-> a-> c-> b-> end],如果最好将其设置为目标(请记住,它来自该节点模式)。向后处理排列:

  • [开始-> a-> b-> c->结束]
  • [开始-> a-> c-> b->结束]
  • [开始-> b-> a-> c->结束]
  • [开始-> b-> c-> a->结束]
  • [开始-> c-> a-> b->结束]
  • [开始-> c-> b-> a->结束]

其中之一将最短。

(“多次访问”的节点在哪里(如果有)?它们只是隐藏在最短路径初始化步骤中。a和b之间的最短路径可能包含c甚至是端点。您无需关心)


由于访问路径最短,因此仅需要访问一次。
阿祖斯(Aziuth)

嗯 一分钟前我很确定,但是,是的,你是对的。显然不是在有几个分支的树中。但是,如果我们将问题抽象为一个仅包含完全连接的mustpass节点的新图形,并且该节点具有原始图形中最短路径的距离,则得出TSP。因此,您确定它不是NP硬性的吗?我假设在一般问题中,必须通过节点的数量取决于总节点的数量,并且例如,如果总数是必须通过的多项式,我们得到NP硬度,对吗?
阿齐斯(Aziuth)

从a-> b出发的路线可能会通过c。因此,没有别的阻止。这只是排列。
bjorke

是?但是,如果我们假设必须通过节点的数量与总节点的数量有某种联系,例如“总节点是必须通过节点的多项式”,那么置换就是O(n!)。您只是用蛮力解决了TSP。
阿兹斯(Aziuth)2016年


1

问题以ANY顺序谈论必须通过。我一直在尝试搜索有关必须通过的节点的已定义顺序的解决方案。我找到了答案,但由于关于StackOverflow的任何问题都没有类似的问题,因此我在此发布,以使最大的人受益。

如果定义了顺序或必须通过,则可以多次运行dijkstra的算法。例如,假设您必须从spass through开始k1k2然后k3(按各自的顺序)在处停止e。然后,您可以做的是在每对连续的节点对之间运行dijkstra的算法。在成本路径由下式给出:

dijkstras(s, k1) + dijkstras(k1, k2) + dijkstras(k2, k3) + dijkstras(k3, 3)


0

如何在十二个“必访”节点上使用蛮力。您可以很容易地覆盖12个节点的所有可能组合,这为您提供了一个最佳电路,可以用来覆盖它们。

现在,您的问题已简化为找到从起始节点到电路的最佳路线之一,然后围绕该路线直至覆盖所有路线,然后找到从该终点到终点的路线。

最终路径包括:

开始->电路路径*->必须访问节点的电路->结束路径*->结束

您会发现我用*标记的路径是这样的

从起始节点到电路上的每个点进行A *搜索,对于每个节点,从电路的下一个和上一个节点到末端进行A *搜索(因为您可以沿任一方向跟踪电路)最终会有很多搜索路径,您可以选择成本最低的搜索路径。

通过缓存搜索还有很多优化的空间,但是我认为这将产生好的解决方案。

但是,它并没有在寻找最佳解决方案的任何地方,因为这可能涉及将必须访问电路留在搜索范围内。


0

在任何地方都没有提到的一件事是,在路径中多次访问同一顶点是否可以。大部分的答案在这里假定它是确定以多次访问相同的优势,但我采取所给的问题(一个路径不应访问同一个顶点不止一次!)是,它是正常访问同一个顶点两次。

因此,仍然可以使用蛮力方法,但是当您尝试计算路径的每个子集时,必须删除已经使用的顶点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.