【问题标题】:Algorithm to find two points furthest away from each other找到彼此最远的两点的算法
【发布时间】:2010-10-03 10:02:02
【问题描述】:

我正在寻找一种用于我正在制作的赛车游戏中的算法。地图/关卡/轨道是随机生成的,所以我需要找到两个位置,起点和目标,以充分利用地图。

  • 算法在二维空间内工作
  • 从每个点只能从四个方向遍历到下一个点;上、下、左、右
  • 点只能被阻塞或非阻塞,只能遍历非阻塞点

关于距离的计算,它不应该是“鸟道”,因为缺少更好的词。如果 A 和 B 之间有墙(或其他阻挡区域),则 A 和 B 之间的路径应该更长。

我不确定从哪里开始,非常欢迎 cmets 并且建议的解决方案在伪代码中是首选。

编辑:正确。在浏览了gs's code 之后,我又试了一次。这次我用 C++ 编写而不是 python。但是,即使在阅读了Dijkstras algorithmfloodfillHosam Alys solution 之后,我仍然没有发现任何关键的区别。我的代码仍然有效,但没有你看起来运行的那么快。完整来源在pastie。唯一有趣的行(我猜)是第 78-118 行的 Dijkstra 变体本身。

但速度不是这里的主要问题。如果有人愿意指出算法中的差异,我将非常感谢您的帮助。

  • 在 Hosam Alys 算法中,他从边界而不是每个节点扫描的唯一区别是?
  • 在 Dijkstras 中,您会跟踪并覆盖步行距离,但不会在洪水填充中,但仅此而已?

【问题讨论】:

  • 你的地图/关卡/轨迹是什么样的?例如,它是矩形的吗?可以分成大小均匀的正方形吗?
  • 是的。关卡上的每个点/图块都是一个正方形,在所有四个方向上都有可能的路径,假设相邻点/图块没有被阻挡。
  • 地图可以有多大?最多多少分?
  • 不会很大。但理论上,没有限制。在我的应用程序中,两个轴上的 150-200 点将被视为“大”。
  • 嗯,你需要多快?我想 O(n^3) 内的任何东西都可以,不是吗?

标签: algorithm math path distance path-finding


【解决方案1】:

假设地图是矩形的,你可以循环遍历所有的边界点,并开始一个泛洪填充以找到距离起点最远的点:

bestSolution = { start: (0,0), end: (0,0), distance: 0 };
for each point p on the border
    flood-fill all points in the map to find the most distant point
    if newDistance > bestSolution.distance
        bestSolution = { p, distantP, newDistance }
    end if
end loop

我猜这应该是O(n^2)。如果我没记错的话,是(L+W) * 2 * (L*W) * 4,其中L是地图的长度,W是地图的宽度,(L+W) * 2表示周长上的边界点数,(L*W)是点数, 和4 是假设洪水填充将访问一个点最多 4 次(从所有方向)。由于n相当于点数,所以这就相当于(L + W) * 8 * n,应该比O(n2)好。 (如果地图是正方形,则顺序为O(16n1.5)。)

更新:根据 cmets,由于地图更像是一个迷宫(而不是像我最初想的那样有简单障碍的迷宫),您可以在上面进行相同的逻辑,但检查所有点在地图中(而不是仅在边界上的点)。这个顺序应该是O(4n2),还是比F-W和Dijkstra都好。

注意:Flood filling 更适合这个问题,因为所有的顶点都直接通过 4 个边界相连。地图的广度优先遍历可以相对快速地产生结果(仅O(n))。我假设每个点都可以从它的 4 个邻居中的每一个中检查洪水填充,因此是上面公式中的系数。

更新 2:感谢我收到的有关此算法的所有积极反馈。特别感谢@Georg his review

附:欢迎任何 cmets 或更正。

【讨论】:

  • 不保证所有边界点都属于最长距离。很有可能,但根据迷宫生成器的不同,可能还有其他解决方案。
  • 你是对的@gs。我不认为它是一个“迷宫”,而是一个更简单的地图(有障碍物)。但是如果修改算法来检查每个点(而不仅仅是边界点),这不就是 O(n^2)(与 FW 的 O(n^3) 相比)吗?
  • 地图不会有那么多障碍物,因为它有复杂的边缘。边缘本身很少是直的。
  • 你可以认为“边缘”就是我所说的“障碍”。它不应该真的有所作为。但是,您的澄清表明它可能是一个迷宫,所以我希望有人能尽快回答我对@gs 的问题。
  • @mizipzor:实现 F-W 应该相当简单。 for (int i = 0; i
【解决方案2】:

跟进关于 Floyd-Warshall 或Hosam Aly 的简单算法的问题:

我创建了一个可以使用这两种方法的测试程序。这些是文件:

在所有测试案例中,Floyd-Warshall 的速度都慢了很多,这可能是因为帮助该算法实现这一目标的边数量非常有限。

这些是时代,每次田地都是四人组,10个田地中有3个是障碍物。

尺寸 Hosam Aly Floyd-Warshall (10x10) 0m0.002s 0m0.007s (20x20) 0m0.009s 0m0.307s (40x40) 0m0.166s 0m22.052s (80x80) 0m2.753s - (160x160) 0m48.028s -

Hosam Aly 的时间似乎是二次的,因此我建议使用该算法。 此外,Floyd-Warshall 的内存消耗为 n2,显然超过了需要。 如果您知道 Floyd-Warshall 为何如此缓慢,请发表评论或编辑此帖子。

PS:好久没写C或者C++了,希望没有犯太多错误。

【讨论】:

  • 看看你的时间,你编码的 H-A 算法是 O(n^2),而你写的 F-W 算法是 O(n^3)。可能你贴错了标签?
  • @casualcoder:不,据我所知,这实际上是正确的。 “H-A”只是广度优先搜索,是O(n),所以运行n次是O(n^2)。似乎 F-W 在稀疏图上实际上非常慢(就像我们在这里一样——每个顶点只有 4 条边),但对于密集图来说要快得多。
  • @gs:您的代码对我来说看起来不错,但有一个小错误:H-A 代码将距离存储在您检查“。”的同一位置。指示位置“未阻塞”的字符 - 您需要为这些单独的数组,否则只要距离可以 >= 46('.' 的 ASCII 代码),您的代码就会失败!
  • @randomhacker:这就是距离为负数的原因。如果我要再次编写代码,我会使用单独的数组。这也比每次都复制旧地图要快。
  • 非常感谢@gs 的评论。关于弗洛伊德的算法,我还有一些想讨论的事情。如果你做一个小的修改,通过使循环在k 上计数 4 次,并更改内部循环中的 if 条件以明确检查 4 个方向?
【解决方案3】:

听起来你想要的是由graph diameter 分隔的端点。一个相当好且易于计算的近似值是选择一个随机点,找到离该点最远的点,然后找到离那里最远的点。这最后两点应该接近最大分离。

对于一个矩形迷宫,这意味着两个洪水填充应该为您提供一对相当好的起点和终点。

【讨论】:

  • 如果首先选择的点被墙壁包围并且只能到达其他 3 个点,这可能会产生非常糟糕的结果。
  • 在这种情况下可以,因为所有节点都将被连接。从地图的生成方式保证。
【解决方案4】:

我删除了我原来推荐 Floyd-Warshall 算法的帖子。 :(

gs did a realistic benchmark 你猜怎么着,对于典型的地图尺寸,F-W 比 Hosam Aly 的“洪水填充”算法慢得多!因此,即使 F-W 是一种很酷的算法,并且比 Dijkstra 的密集图快得多,但我不能再推荐它来解决 OP 的问题,因为它涉及非常稀疏的图(每个顶点只有 4 条边)。

记录在案:

  • Dijkstra's algorithm 的有效实现需要 O(Elog V) 时间来处理具有 E 条边和 V 个顶点的图。
  • Hosam Aly 的“洪水填充”是breadth first search,即 O(V)。这可以被认为是 Dijkstra 算法的一个特例,其中没有一个顶点可以修改其距离估计。
  • Floyd-Warshall algorithm 花费 O(V^3) 时间,非常容易编码,并且对于密集图(顶点通常连接到许多其他顶点的图)来说仍然是最快的。但对于 OP 的任务,它不是正确的选择,因为它涉及非常稀疏的图。

【讨论】:

  • 我支持这个,我也删除了我关于F-W的帖子。
  • 它有 13 个赞成票,我认为没有足够多的人反对它。
  • 好吧,我不能对自己的帖子投反对票,而且如果有 7 人投了赞成票,可能需要一段时间才能投反对票。摆脱它可以避免误导人们。
【解决方案5】:

Raimund Seidel 在他的论文 On the All-Pairs-Shortest-Path Problem in Unweighted Undirected Graphs [pdf] 的第一部分中提供了一种使用矩阵乘法来计算未加权无向图上的所有对距离矩阵的简单方法(这正是您想要的)。

输入是邻接矩阵,输出是全对最短路径距离矩阵。对于 n 个点,运行时间为 O(M(n)*log(n)),其中 M(n) 是矩阵乘法算法的运行时间。

如果您也需要,本文还提供了计算实际路径(在同一运行时)的方法。

Seidel 的算法很酷,因为运行时间与边数无关,但实际上我们并不关心这里,因为我们的图是稀疏的。但是,如果您想要所有对距离矩阵,这可能仍然是一个不错的选择(尽管运行时间比 n^2 稍差),而且这也可能比迷宫上的洪水填充更容易实现和调试。

这是伪代码:

Let A be the nxn (0-1) adjacency matrix of an unweighted, undirected graph, G

All-Pairs-Distances(A)
    Z = A * A
    Let B be the nxn matrix s.t. b_ij = 1 iff i != j and (a_ij = 1 or z_ij > 0)
    if b_ij = 1 for all i != j return 2B - A //base case
    T = All-Pairs-Distances(B)
    X = T * A
    Let D be the nxn matrix s.t. d_ij = 2t_ij if x_ij >= t_ij * degree(j), otherwise d_ij = 2t_ij - 1
    return D

要获得距离最大的点对,我们只需返回 argmax_ij(d_ij)

【讨论】:

  • 很有趣,但是 sub-O(n^3) 矩阵乘法算法难以编码并且开销很大——根据维基百科,O(n^2.376) 算法“对于适合当今计算机的矩阵。”所以在实践中你会使用 O(n^3) 算法,这比 F-W 慢。
【解决方案6】:

完成了该问题的 dijkstra 解决方案的 python 模型。 代码有点长,所以我把它贴在别的地方:http://refactormycode.com/codes/717-dijkstra-to-find-two-points-furthest-away-from-each-other

在我设置的大小下,运行一个节点的算法大约需要 1.5 秒。为每个节点运行它需要几分钟。

但似乎不起作用,它总是将左上角和右下角显示为最长的路径; 58 块瓷砖。当您没有障碍时,这当然是正确的。但是即使添加了几个随机放置的,程序仍然会找到最长的一个。也许它仍然是正确的,如果没有更高级的形状就很难测试。

但也许它至少可以显示我的野心。

【讨论】:

  • 这使用 n^3,因此不是一个很好的算法。使用 Hosam Aly 的算法。
  • 看来我至少现在开始工作了。但这需要很长时间。明天我会调查你的代码@gs。希望我能做得更快。 refactormycode.com/codes/…
【解决方案7】:

好的,“Hosam 算法”是在节点上预选的广度优先搜索。 Dijkstra 的算法不应该在这里应用,因为你的边没有权重。

差异至关重要,因为如果边的权重不同,您需要保持许多选项(备用路线)打开并在每一步都检查它们。这使得算法更加复杂。 使用广度优先搜索,您只需探索一次所有边,确保您找到到每个节点的最短路径。即按照找到它们的顺序探索边缘。

所以基本上区别在于 Dijkstra 必须“回溯”并查看它之前探索过的边缘以确保它遵循最短路径,而广度优先搜索始终知道它遵循最短路径。

此外,在迷宫中,外边界上的点并不能保证是最长路线的一部分。 例如,如果你有一个巨大螺旋形状的迷宫,但外端回到中间,你可以有两个点,一个在螺旋的中心,另一个在螺旋的末端,两者在中间!

因此,一个很好的方法是从每个点使用广度优先搜索,但在搜索后删除起点(您已经知道往返它的所有路线)。 广度优先的复杂度是 O(n),其中 n = |V|+|E|。我们对 V 中的每个节点都这样做一次,所以它变成了 O(n^2)。

【讨论】:

    【解决方案8】:

    您的描述在我看来像是maze routing 问题。查看Lee Algorithm。有关 VLSI 设计中的布局布线问题的书籍可能会对您有所帮助 - Sherwani's "Algorithms for VLSI Physical Design Automation" 很好,您可能会发现 VLSI Physical Design Automation by Sait and Youssef 有用(而且在 Google 版本中更便宜...)

    【讨论】:

    • 迷宫路由不需要定义起点和目标吗?或者您是否建议我为每个节点对每个其他节点执行李算法,类似于提议的 Dijkstra 解决方案?
    • 这对你来说太慢了。此算法会找到路,但并未针对寻找距离进行优化。
    • 迷宫路线需要定义起点和目标。它的优点是它确实考虑了障碍。我建议您使用启发式方法 - 从具有最大“鸟飞”距离的点对开始,然后针对这些对运行 Lee 算法。
    【解决方案9】:

    如果您的对象(点)不经常移动,您可以在比 O(n^3) 更短的时间内执行这样的计算。

    您所需要做的就是将空间分成大网格并预先计算网格间距离。然后选择占据最远网格的点对是一个简单的查表问题。在一般情况下,您只需要成对检查一小部分对象。

    如果距离度量是连续的,则此解决方案有效。因此,例如,如果地图中有许多障碍(如迷宫),则此方法可能会失败。

    【讨论】:

    • 我能问一下,当地图像迷宫时,这怎么可能?
    • 对于每个网格,您需要计算入口/出口点(用于通道)以及从子网格内的任何兴趣点到该网格出口的距离。也不是这里列出的最糟糕的方法。
    猜你喜欢
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 2013-01-10
    • 2022-11-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2011-02-13
    相关资源
    最近更新 更多