【问题标题】:A* Algorithm for very large graphs, any thoughts on caching shortcuts?A* 非常大图的算法,关于缓存快捷方式的任何想法?
【发布时间】:2015-06-21 19:26:01
【问题描述】:

我正在 OpenStreetMap 地图上编写快递/物流模拟,并意识到如下图所示的基本 A* 算法对于大型地图(如大伦敦)来说不够快。

绿色节点对应于放入开放集/优先队列中的节点,由于数量庞大(整个地图约为 1-2 百万),需要 5 秒左右才能找到图中所示的路线。不幸的是,每条路由 100 毫秒是我的绝对限制。

目前,节点既存储在邻接列表中,也存储在空间 100x100 2D 数组中。

我正在寻找可以权衡预处理时间、空间以及(如果需要)路线的最优性以实现更快查询的方法。根据分析器,启发式成本的直线 Haversine 公式是最昂贵的函数 - 我已经尽可能优化了我的基本 A*。

例如,我在想如果我从二维数组的每个象限中选择一个任意节点 X 并在每个象限之间运行 A*,我可以将路线存储到磁盘以供后续模拟。查询时,我只能在象限中​​运行 A* 搜索,以在预先计算的路线和 X 之间进行搜索。

我上面描述的内容是否有更完善的版本,或者我应该采用不同的方法。非常感谢!

作为记录,以下是一些基准结果,用于任意加权启发式成本并计算 10 对随机选取的节点之间的路径:

Weight // AvgDist% // Time (ms)
1       1       1461.2
1.05    1       1327.2
1.1     1       900.7
1.2     1.019658848     196.4
1.3     1.027619169     53.6
1.4     1.044714394     33.6
1.5     1.063963413     25.5
1.6     1.071694171     24.1
1.7     1.084093229     24.3
1.8     1.092208509     22
1.9     1.109188175     22.5
2       1.122856792     18.2
2.2     1.131574742     16.9
2.4     1.139104895     15.4
2.6     1.140021962     16
2.8     1.14088128      15.5
3       1.156303676     16
4       1.20256964      13
5       1.19610861      12.9

令人惊讶的是,将系数增加到 1.1 几乎将执行时间减半,同时保持相同的路线。

【问题讨论】:

  • 我觉得你应该去那里问问:cs.stackexchange.com
  • 对算法的修改,允许对每个路段进行加权(例如,8 车道高速公路每单位距离的成本为 1,而未铺砌的土路的成本为 50,并且所有的东西都在之间...)将是一个可能的起点。尽管您随后需要对从地图提供商处获得的所有路段进行分类,但如果他们还没有与之关联的合适数据...
  • 没有时间写一个完整的答案,但最先进的技术涉及找到小的分隔符。不要满足于近似结果——调试起来很烦人。
  • 您对一对一路线或计算距离矩阵感兴趣吗?或许你可以在这里找到一些提示:stackoverflow.com/questions/430142
  • 尝试使用“Arc-Flags”对图形进行预处理;这很简单,也应该给你一个很好的加速。

标签: algorithm openstreetmap graph-algorithm shortest-path a-star


【解决方案1】:

您应该能够通过权衡最优性来使其更快。请参阅维基百科上的 Admissibility and optimality。

这个想法是使用epsilon 值,这将导致解不比1 + epsilon 乘以最佳路径差,但会导致算法考虑的节点更少。请注意,这并不意味着返回的解决方案总是1 + epsilon 乘以最佳路径。这只是最坏的情况。我不知道它在实践中会如何解决您的问题,但我认为值得探索。

维基百科为您提供了许多依赖于此想法的算法。我相信这是您改进算法的最佳选择,并且它有可能在您的时间限制内运行,同时仍然返回良好的路径。

由于您的算法确实在 5 秒内处理了数百万个节点,我假设您也使用二进制堆来实现,对吗?如果您手动实现它们,请确保它们被实现为简单数组并且它们是二进制堆。

【讨论】:

  • 谢谢,我没想过要尝试它,它提高了相当多的速度而没有太多的惩罚。仅将启发式成本乘以 1.5 即可在 200 毫秒内行驶 91.8 公里,而在 5 秒内行驶 88.3 公里。随着算法的运行,我将进一步试验改变这一点。
  • 我尝试过使用 .net SortedList 和来自库的基于数组的二进制堆,并且列表稍微快了一点。
  • @drspa44 - 如果您使用的是 .net,您是否尝试过添加并行性?也许尝试将其添加到更新节点邻居的部分。我不确定它是否会有所帮助,但值得尝试用 Parallel.For 替换 for 循环。
  • 我确实在将使用 A* 的实体上使用了 Parallel.ForEach,因此使用了所有 CPU 能力。我认为小规模并行化不会产生太大影响,尤其是在同步和开销方面。
  • 那里一定有问题。 .net SortedList<TKey, TValue> 是使用排序数组实现的,它有 O(n) 用于插入/删除(除非您总是在数组底部添加项目)。正确实施后,堆具有O(log n)。它应该更快。检查那里有一个相当好的最大堆实现:referencesource.microsoft.com/#System.Core/System/Linq/Parallel/… 对于A*,你需要相反的,但它很容易适应。
【解决方案2】:

针对这个问题有专门的算法可以进行大量的预计算。从内存中,预计算将信息添加到 A* 用来生成比直线距离更准确的启发式的图表中。维基百科在http://en.wikipedia.org/wiki/Shortest_path_problem#Road_networks 给出了许多方法的名称,并说 Hub Labeling 是领导者。对此进行快速搜索会出现http://research.microsoft.com/pubs/142356/HL-TR.pdf。使用 A* 的旧版本位于 http://research.microsoft.com/pubs/64505/goldberg-sp-wea07.pdf。

您真的需要使用 Haversine 吗?为了覆盖伦敦,我原以为您可以假设地球是平的并使用毕达哥拉斯,或者将每个链接的长度存储在图中。

【讨论】:

【解决方案3】:

Microsoft Research 就该主题写了一篇非常棒的文章:

http://research.microsoft.com/en-us/news/features/shortestpath-070709.aspx

原始论文托管在此处(PDF):

http://www.cc.gatech.edu/~thad/6601-gradAI-fall2012/02-search-Gutman04siam.pdf

基本上你可以尝试几件事:

  1. 从源和目标开始。这有助于最大限度地减少您在从源头向外穿越到目的地时所浪费的工作量。
  2. 使用地标和高速公路。本质上,在每张地图中找到一些通常采用路径的位置,并执行一些预计算以确定如何在这些点之间有效导航。如果您能找到从源头到地标、再到其他地标、再到目的地的路径,您可以快速找到可行的路线并从那里进行优化。
  3. 探索“到达”算法等算法。这有助于最大限度地减少您在遍历图形时要做的工作量,方法是最大限度地减少为找到有效路线而需要考虑的顶点数量。

【讨论】:

  • 谢谢。我确实读过那篇文章,今天我试图想出一些方法来实现某种形式的地标方法。我的问题是不知道如何根据我拥有的数据选择地标。高速公路对我来说也不是那么容易识别的。我确实尝试过达到,但我发现性能提升可以忽略不计。看看微软的图片,双向 Djikstra 和 A* 似乎并没有比我现在的快很多,尽管我还没有实现。
  • 第 2 步称为收缩层次结构。
  • @MSalters 与地标是不同的野兽-> ALT
【解决方案4】:

我认为用“象限”来解决你的想法是值得的。更严格地说,我称之为低分辨率路线搜索。

您可以选择 X 个足够接近的连接节点,并将它们视为单个低分辨率节点。将你的整个图表分成这样的组,你会得到一个低分辨率的图表。这是一个准备阶段。

为了计算从源到目标的路径,首先确定它们所属的低分辨率节点,然后找到低分辨率路径。然后通过在高分辨率图上查找路线来改进您的结果,但是将算法限制为仅属于低分辨率路线的 hte 低分辨率节点的节点(可选地,您也可以考虑到一定深度的相邻低分辨率节点)。

这也可以推广到多种分辨率,而不仅仅是高/低。

最后,您应该得到一条足够接近最优路线的路线。它是局部最优的,但在某种程度上可能比全局最优略差,这取决于分辨率跳跃(即当一组节点被定义为单个节点时所做的近似)。

【讨论】:

  • 谢谢,如果我遵循这种方法,我会记住这个答案。
【解决方案5】:

这里有几十种 A* 变体可能符合要求。不过,您必须考虑您的用例。

  • 您是否受到内存(以及缓存)的限制?
  • 你能并行搜索吗?
  • 您的算法实施是否仅在一个地方使用(例如大伦敦,而不是纽约市或孟买或其他任何地方)?

我们无法知道您和您的雇主知道的所有详细信息。因此,您的第一站应该是CiteSeer 或 Google Scholar:寻找与您具有相同一般约束条件的寻路论文。

然后选择三个或四个算法,进行原型设计,测试它们如何扩大规模并对其进行微调。您应该记住,您可以根据点之间的距离、剩余时间或任何其他因素在同一个大型寻路程序中组合各种算法。

正如已经说过的,基于您的目标区域的小规模,放弃 Haversine 可能是您节省宝贵时间进行昂贵的三角评估的第一步。注意:我不建议在纬度、经度坐标中使用欧几里德距离 - 将您的地图重新投影到例如靠近中心的横向墨卡托,并使用以码或米为单位的笛卡尔坐标!

预计算是第二个,更改编译器可能是一个明显的第三个想法(切换到 C 或 C++ - 有关详细信息,请参阅 https://benchmarksgame.alioth.debian.org/)。

额外的优化步骤可能包括摆脱动态内存分配,并使用高效的索引在节点之间进行搜索(想想 R-tree 及其衍生/替代方案)。

【讨论】:

    【解决方案6】:

    GraphHopper 做了两件事来获得快速、非启发式和灵活的路由(注意:我是作者,你可以在线尝试here)

    1. 一个不太明显的优化是避免 OSM 节点到内部节点的 1:1 映射。相反,GraphHopper 仅使用联结作为节点,并节省了大约 1/8 的遍历节点。
    2. 它有高效的 A*、Dijkstra 或例如一对多 Dijkstra。这使得在 1 秒内穿越整个德国的路线成为可能。 A* 的(非启发式)双向版本使这一过程更快。

    因此,应该可以为您提供前往大伦敦的快速路线。

    此外,默认模式是速度模式,它使一切速度提高一个数量级(例如,欧洲宽幅路线为 30 毫秒)但灵活性较低,因为它需要预处理 (Contraction Hierarchies)。如果您不喜欢这个,只需禁用它并进一步微调包含的汽车街道,或者可能更好地为卡车创建新配置文件 - 例如排除服务街道和轨道,这应该会给你带来 30% 的额外提升。与任何双向算法一样,您可以轻松实现并行搜索。

    【讨论】:

      【解决方案7】:

      我曾在一家大型导航公司工作,因此我可以自信地说,即使在嵌入式设备上,100 毫秒也应该可以让您获得从伦敦到雅典的路线。大伦敦将是我们的测试地图,因为它很小(很容易放入 RAM - 这实际上不是必需的)

      首先,A* 已经完全过时了。它的主要好处是它“技术上”不需要预处理。在实践中,无论如何您都需要对 OSM 映射进行预处理,这样毫无意义。

      给你一个巨大的速度提升的主要技术是弧形标志。如果将地图划分为 5x6 部分,则可以为每个部分分配 32 位整数中的 1 位位置。现在,您可以确定每条边在 to 从另一个部分{X,Y} 行驶时是否有用。很多时候,道路是双向的,这意味着只有两个方向中的一个是有用的。因此,两个方向之一设置了该位,另一个将其清除。这可能看起来不是一个真正的好处,但这意味着在许多交叉路口,您可以将要考虑的选择数量从 2 个减少到仅 1 个,并且只需要一个位操作。

      【讨论】:

      • 对于伦敦来说,一个主要优势是您也可以在桥梁上设置这些位。 A* 可能会因不必要地过河而被卡在对岸而受苦。
      【解决方案8】:

      通常 A* 伴随着过多的内存消耗而不是时间冲突。

      但是我认为首先仅使用属于“大街道”的节点进行计算可能会很有用,您通常会选择一条高速公路而不是一条小巷。

      我猜你可能已经将它用于你的权重函数,但如果你使用一些优先队列来决定接下来要测试哪个节点以进行进一步的旅行,你可以更快。

      您还可以尝试将图形简化为仅属于低成本边的节点,然后找到从开始/结束到最近这些节点的方法。 所以你有两条从开始到“大街道”和“大街道”到结束的路径。 您现在可以计算两个节点之间的最佳路径,这两个节点是简化图中“大街道”的一部分。

      【讨论】:

        【解决方案9】:

        老问题,但还没有:

        尝试使用与“二进制堆”不同的堆。 “最佳渐近复杂性堆”绝对是斐波那契堆,它的 wiki 页面有一个很好的概述:

        https://en.wikipedia.org/wiki/Fibonacci_heap#Summary_of_running_times

        请注意,二进制堆的代码更简单,而且它是通过数组实现的,并且数组的遍历是可预测的,因此现代 CPU 执行二进制堆操作的速度要快得多。

        但是,如果数据集足够大,其他堆将胜过二叉堆,因为它们的复杂性...

        这个问题看起来数据集够大了。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-20
          • 1970-01-01
          • 1970-01-01
          • 2018-08-12
          • 1970-01-01
          相关资源
          最近更新 更多