【问题标题】:Algorithm for nearest point最近点的算法
【发布时间】:2011-09-03 11:53:27
【问题描述】:

我有一个约 5000 个点的列表(指定为经度/纬度对),我想找到其中最接近用户指定的另一个点的 5 个点。

谁能提出一个有效的算法来解决这个问题?我正在用 Ruby 实现这个,所以如果有一个合适的库,那会很高兴,但我仍然对算法感兴趣!

更新:有几个人要求提供有关该问题的更具体细节。所以这里是:

  • 5000点大多在同一个城市。外面可能有一些,但可以肯定的是,其中 99% 位于 75 公里半径范围内,并且全部位于 200 公里半径范围内。
  • 点列表很少更改。为了争论,假设它每天更新一次,我们必须在这段时间内处理几千个请求。

【问题讨论】:

  • 如果是那几个点,一个一个去就好了。
  • 无论您选择哪种算法,您都可以通过比较平方距离而不是实际距离来节省一些时间。如果您不需要知道实际距离,则无需执行平方根运算。

标签: ruby algorithm location


【解决方案1】:

您可以通过使用quad-treekd-tree 划分二维空间来加速搜索,然后在到达叶节点后逐一比较剩余距离,直到找到最接近的匹配。

另请参阅 this blog post,它指的是 this other blog post,它们都讨论了在 Ruby 中使用 kd-trees 进行最近邻搜索。

【讨论】:

  • 总的来说 - 一个好主意,但使用 5000 个点创建数据结构比手动计算所有可能的距离需要更长的时间。
【解决方案2】:

您可以使用曼哈顿距离(按纬度缩放)获得一个非常快速上界估计距离,这应该足以拒绝 99.9% 的候选人(如果他们不接近)(编辑:从那以后你告诉我们他们很接近。在这种情况下,你的度量应该是距离平方,根据 Lars H 的评论)。 考虑这相当于拒绝球形矩形边界框之外的任何东西(作为圆形边界框的近似值)。 我不做 Ruby,所以这里是带有伪代码的算法:

让你的参考点P(pa,po)其他点X(xa,xo)的纬度、经度。 预计算ka,纵向距离的纬度比例因子:ka (= cos(pa in°))。 (严格来说,ka = 常数是 P 附近的线性化近似值。)

那么距离估计为:D(X,P) = ka*|xa-pa| + |xo-po| = ka*da + do

在哪里 |z|表示绝对(z)。在最坏的情况下,这会高估真实距离 √2 倍(当 da==do 时),因此我们允许如下:

进行一次连续搜索并保持 Dmin,这是按比例计算的第五小曼哈顿距离估计值。 因此,您可以预先拒绝所有 D(X,P) > √2 * Dmin 的点(因为它们必须至少比 √((ka*da)² + do²)更远 - 那应消除 99.9% 的点)。 保留所有剩余候选点的列表,其中 D(X,P) 如果您找到新的第五小 D. 优先级队列,则更新 Dmin,否则为(coord,D) 是很好的数据结构。 请注意,我们从未计算欧几里得距离,我们只使用浮点乘法和加法。

(考虑这类似于四叉树,除了过滤掉除我们感兴趣的区域之外的所有内容,因此无需预先计算准确的距离或构建数据结构。)

如果您告诉我们预期的纬度、经度分布(度、分还是什么)会有所帮助?如果所有点都接近,则此估计器中的 √2 因子将过于保守,并将每个点都标记为候选;最好使用基于查找表的距离估计器。)

伪代码:

initialize Dmin with the fifth-smallest D from the first five points in list
for point X in list:
    if D(X,P) <= √2 * Dmin:
        insert the tuple (X,D) in the priority-queue of candidates
        if (Dmin>D): Dmin = D
# after first pass, reject candidates with D > √2 * Dmin (use the final value of Dmin)
# ...
# then a second pass on candidates to find lowest 5 exact distances

【讨论】:

    【解决方案3】:

    由于您的列表很短,我强烈推荐蛮力。只需将所有 5000 与用户指定的点进行比较。它将是 O(n),您将获得报酬。

    除此之外,四叉树或 Kd 树是空间细分的常用方法。但是在你的情况下,你最终会在树中进行线性数量的插入,然后是恒定数量的对数查找......有点浪费,当你最好只做线性数量的距离比较并完成。

    现在,如果你想找到 N 个最近的点,你正在考虑对计算出的距离进行排序并取第一个 N,但这仍然是 O(n log n)ish。

    编辑:值得注意的是,如果您要为多个查询重用点列表,那么构建空间树变得很有价值。

    【讨论】:

      【解决方案4】:

      对于 5000 个节点,我会计算每个节点的单个 x+y 距离,而不是纯暴力计算,而不是直线距离。

      一旦您对该列表进行了排序,例如第 5 个节点的 x+y 为 38,您可以排除 x 或 y 距离大于 38 的任何节点。这样,您可以排除很多节点,而无需计算直线距离。然后蛮力计算剩余节点的直线距离。

      【讨论】:

        【解决方案5】:

        这些算法并不容易解释,因此我只会给你一些正确方向的提示。您应该寻找 Voronoi 图。使用 Voronoi 图,您可以轻松地在 O(n^2 log n) 时间内预先计算图形,并在 O(log n) 时间内搜索最近的点。

        预计算是在晚上通过一项 cron 作业完成的,并且搜索是实时的。这符合您的规范。

        现在您可以保存 5000 个点中每个点的 k 个最接近点对,然后从 Voronoi 图的最近点开始搜索剩余的 4 个点。

        但请注意,这些算法并不容易实现。

        一个很好的参考是:

        • de Berg:计算几何算法应用 (2008) 第 7.1 和 7.2 章

        【讨论】:

          【解决方案6】:

          由于您有这么少的点,我建议您进行蛮力搜索,其效果是使用O(n^2) 操作、n = 5000 或大约 25/2 百万次迭代尝试所有点一个合适的算法,只存储相关的结果。这在 C 中将有不到 100 毫秒的执行时间,所以我们在 Ruby 中最多只需要一两秒。

          当用户选择一个点时,您可以使用您存储的数据在恒定时间内给出结果。

          编辑我重新阅读了您的问题,似乎用户提供了他自己的最后一点。在这种情况下,每次用户提供一个点时,在您的集合中进行 O(n) 线性搜索会更快。

          【讨论】:

            【解决方案7】:

            如果您需要多次重复此操作,使用不同的用户输入位置,但不想实现四叉树(或找不到库实现),那么您可以使用局部敏感哈希 (一种)相当直观的方法:

            • 获取 (x,y) 对并创建两个列表,一个 (x, i) 和一个 (y, i),其中 i 是点的索引
            • 对两个列表进行排序

            那么,当给定一个点 (X, Y),

            • X 和 Y 的二等分排序
            • 在两个列表上向外扩展,寻找共同的索引
            • 对于常用索引,计算精确距离
            • 当 X 和 Y 的差异超过当前 5 个点中最远的确切距离时停止扩展。

            您所做的只是说附近的点必须具有相似的 x 和相似的 y 值...

            【讨论】:

              猜你喜欢
              • 2011-12-07
              • 1970-01-01
              • 2012-07-16
              • 2019-04-15
              • 1970-01-01
              • 2019-02-27
              • 2018-11-20
              • 2020-09-10
              相关资源
              最近更新 更多