我有一个大约有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)
如果有人把这个问题和旅行商问题进行比较,那他们可能没有仔细阅读你的问题。在旅行商问题中,目标是找到访问所有顶点的最短循环(哈密顿环)——它对应于每个节点都被标记为“必经之路”。
在您的情况下,由于您只有大约十几个标记为“必经之路”的点,并且12!非常小(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(n3),对于所有排列部分是O(k!n),100^3+(12!)(100)基本上是小菜一碟,除非你有一些非常严格的限制要求]
运行Dijkstra算法,以找到所有关键节点(起点、终点和必经点)之间的最短路径,然后进行深度优先遍历,可以告诉你通过接触所有节点从起点到达终点的最短路径。
这涉及到两个问题... Steven Lowe指出了这一点,但是没有充分重视问题的后半部分。
首先,您应该发现所有关键节点(起点、终点、必经之路)之间的最短路径。一旦这些路径被发现,您可以构建一个简化图,其中新图中的每条边都是原始图中从一个关键节点到另一个关键节点的路径。有很多可用于查找最短路径的路径规划算法。
然而,一旦您拥有了这个新图,您就会遇到旅行商问题(几乎相同,不需要回到起点)。关于此问题的任何帖子,如上所述,都将适用。
实际上,您发布的问题类似于旅行商问题,但我认为更接近简单的路径查找问题。与需要访问每个节点不同,您只需要在最短时间(距离)内访问特定的一组节点。
原因是,与旅行商问题不同,迷宫不允许您直接从地图上的任何一个点到达另一个点,而无需通过其他节点进行通行。
我实际上建议考虑使用A *路径查找技术。您可以通过决定哪些节点直接访问其他节点以及每个特定节点的“成本”来设置此功能。在这种情况下,由于您的节点相对较近,每个“跳跃”可能具有相等的成本。 A *可以利用此信息找到任何两点之间的最低成本路径。由于您需要从A点到B点,中间还要访问约12个点,即使使用路径查找的蛮力方法也不会有任何影响。
只是值得考虑的另一种选择。它看起来非常像旅行商问题,阅读这些论文很有好处,但仔细观察,您会发现这只是过度复杂化了事情。 ^ _ ^这是来自曾经处理过这些问题的电子游戏程序员的想法。
其中一个将是最短的。
(如果有"被多次访问"的节点在哪里?它们只是隐藏在最短路径初始化步骤中。a和b之间的最短路径可能包含c,甚至是终点。你不需要关心)
Andrew Top有正确的想法:
1)Djikstra算法 2)一些TSP启发式算法。
我推荐Lin-Kernighan启发式算法:它是任何NP完全问题中最知名的之一。唯一需要记住的是,在步骤2扩展图形后,您可能会在扩展路径中遇到环路,因此应该绕过这些环路(查看路径上沿着顶点的度数)。
实际上,我不确定相对于最优解而言,这种解决方案有多好。可能存在一些与短路有关的病态情况。毕竟,这个问题看起来非常像Steiner树:http://en.wikipedia.org/wiki/Steiner_tree,你绝不能仅通过收缩图形并运行Kruskal等算法来近似Steiner树。
考虑到节点和边的数量是相对有限的,您可以计算每条可能的路径并选择最短的一条。
通常称为旅行商问题,无论使用什么算法,它都具有非确定性多项式运行时间。
有一件事情没有在任何地方提到,那就是在路径中同一个顶点是否可以被访问多次。这里大部分的答案都假设同一个边可以被多次访问,但是根据问题(路径不应该重复访问同一个顶点!),我的看法是不能重复访问同一个顶点。
因此,暴力方法仍然适用,但在尝试计算路径的每个子集时,您必须删除已经使用过的顶点。
在十二个“必游”节点上使用蛮力算法如何?您可以轻松地覆盖所有可能的12个节点组合,这将为您留下一个最优电路,您可以按照它来覆盖它们。
现在,您的问题简化为从起始节点到电路找到最佳路径,然后沿着它走到覆盖它们,然后找到从那里到终点的路径。
最终路径由以下组成:
起点 -> 通往电路的路径* -> 必游节点电路 -> 通往终点的路径* -> 终点
您可以像这样找到我标记为*的路径
从起始节点开始进行A *搜索,直到电路上的每个点 对于每个电路上的点,从电路上的下一个和上一个节点到终点进行A *搜索(因为您可以按任意方向跟随电路) 最终您会得到许多搜索路径,您可以选择成本最低的路径。
通过缓存搜索,有很大的优化空间,但我认为这将生成良好的解决方案。
它并没有接近寻找最优解,因为这可能涉及在搜索中离开必游电路。
b
从a
到c
。可能最短的从b
到a
和c
的路径共享一条边。在这种情况下,该边将重复两次。其中一条路径需要比最优解更差,以避免生成循环。 - Pietro SaccardiINF
,所以行d[i][j] = min(d[i][j], d[i][k] + d[k][j])
变为d[i][j] = min(INF, INF + INF)
,所有单元格始终保持等于INF
。您需要添加一步,将该数组填充为来自图形的边长。 - Stef