注意:此答案已根据与 @Danilo Souza Morães 和 @burnabyRails(accepted answer 的作者)就 Danilo 在 CS 网站上提出的类似问题的广泛讨论进行更新,以供将来参考.原始答案的主要问题是我假设这里使用/讨论了一些不同的算法。由于最初的答案为达尼洛提供了一些重要的见解,因此它在最后被保留了下来。此外,如果您要阅读 Danilo 在他的回答中提到的discussion,请务必先阅读此处的介绍,以更好地理解我的意思。添加的前言讨论了相关算法的不同版本。
简介
该算法基于两个主要思想:
- 典型的递归分治法
- 事实上,如果您已经知道左右区域的最佳距离,您可以在
O(N) 时间执行“合并”步骤,因为您可以证明您可以在O(1) 时间对每个点进行检查.
尽管如此,在实践中仍有多种方法可以实现相同的基本思想,尤其是组合步骤。主要区别在于:
- 您是否会以不同的方式独立处理“左”点和“右”点?
- 您是针对每个点进行固定数量的检查,还是先过滤候选者,然后只对过滤后的候选者进行距离检查?请注意,如果您可以确保在摊销
O(1) 中完成过滤,您可以在不破坏 O(N*log(N)) 时间的情况下进行一些过滤。换句话说,如果您知道优秀候选人的数量有一个很大的上限,您就不必准确检查候选人的数量。
CLRS Introduction to Algorithms 中算法的经典描述清楚地回答了问题 #1,因为您将“左”和“右”点混合在一起并共同对待它们。至于#2,答案不太清楚(对我来说),但它可能意味着检查一些固定数量的点。这似乎是达尼洛提出问题时想到的版本。
我想到的算法在这两点上都不同:它向上通过“左”点,并对照所有过滤的“右”候选点检查它们。显然,在我写答案时以及在聊天中进行初步讨论时,我并没有意识到这种差异。
“我的”算法
这是我想到的算法的草图。
开始步骤很常见:我们已经处理了“左”和“右”点,并且知道目前为止最好的?,我们将它们沿着Y 轴排序。我们还过滤掉了所有不在±? 条带中的点。
外循环是我们通过“左”点向上。现在假设我们处理一个,我们称之为“左点”。此外,在必要时,我们会半独立地迭代移动“起始位置”的“正确”点(参见步骤 #2)。
1234563 Y 轴。 (注意:分离“左”点和“右”点是必须从-? 开始的原因)
现在从该起始位置继续向上并计算与当前左侧点位于+? 之前的所有点的距离。
在第 2 步和第 3 步中完成的过滤使其“依赖于数据”。此实现中的权衡是,您以更多Y-checks 为代价进行更少的距离检查。此外,代码可能更复杂。
为什么这种组合算法适用于O(N)?出于同样的原因,在矩形?x2? 中可以容纳多少个点有一个固定界限(即O(1)),这样每两个点之间的距离至少为?。这意味着将在第 3 步进行O(1) 检查。实际上,该算法检查的所有距离都将由 CLRS 版本检查,并且根据数据,可能还会检查更多距离。
第 2 步的摊销成本也是 O(1),因为第 2 步在整个外循环上的总成本是 O(n):您显然不能将起始位置向上移动的次数超过总共“正确的点”。
改进的 CLRS 算法
即使在算法的 CLRS 版本中,您也可以轻松地对 #2 做出不同的决定。关键步骤的描述说:
- 对于数组
Y′ 中的每个点p,算法会尝试在Y′ 中找到位于δ 单位p 内的点。正如我们很快将看到的,只需要考虑Y′ 中紧随p 的7 个点。该算法计算从 p 到每个 7 点的距离,并跟踪在 Y′ 中的所有点对中找到的最近对距离 δ′
很容易修改为不检查7点,而是首先检查Y的差异是否小于?。显然,您仍然保证需要检查不超过7 点。同样的权衡是你做的距离检查更少,但做一些Y-difference 检查。根据您的数据和硬件上不同操作的相对性能,这可能是一个好坏的权衡。
其他一些重要的想法
如果您不需要找到所有距离最小的对,您可以在过滤时安全地使用<?`` instead of
在使用浮点数表示的真实硬件世界中,= 的概念并不那么清楚。通常你需要检查类似abs(a - b) < έ 的东西。
我的反例背后的想法(针对不同的算法):您不必将所有点都放在边缘上。边为? 的等边三角形可以放入大小为?-έ 的正方形中(即<? 的另一种说法)
原答案
我认为您没有正确理解该算法中常量7 的含义。实际上,您不会检查4 或7 或任何固定数量的点,而是沿着Y 轴向上(或向下)并检查落入相应矩形的点。很容易可能只有0 这样的点。对于算法在承诺的O(n*log(n)) 时间内运行真正重要的是,在该步骤检查的点数有一个固定上限。只要你能证明它,任何 constant 上限都可以工作。换句话说,重要的是该步骤是O(n),而不是特定的常数。 7 只是一个比较容易证明的,仅此而已。
我相信7 的实际原因是,在真实硬件上,您无法精确计算浮点数据类型中的距离,您肯定会遇到一些舍入误差。这就是为什么使用< 而不是<= 并不实用。
最后我不认为4 是一个正确的界限,假设你可以可靠地做到<。假设? = 1。让“左”点为(-0.0001; 0),因此“右”< 矩形为0 <= x < 1 和-1 < y < 1。考虑以下 5 个点(想法是:几乎在角落的 4 个点正好适合矩形< ? 和它们之间的距离> ?,第 5 个在矩形的中心):
-
P1 = (0.001; 0.999)
-
P2 = (0.999; 0.93)
-
P3 = (0.001; -0.999)
-
P4 = (0.999; -0.93)
-
P5 = (0.5; 0)
请注意,这 5 个点之间的距离应大于?,其中一些距离“左”点可能小于?。这就是我们首先检查它们的原因。
距离P1-P2(和对称的P3-P4)是
sqrt(0.998^2 + 0.069^2) = sqrt(0.996004 + 0.004761) = sqrt(1.000765) > 1
距离P1-P5(和对称的P3-P5)是
sqrt(0.499^2 + 0.999^2) = sqrt(0.249001 + 0.998001) = sqrt(1.247002) > 1
距离P2-P5(和对称的P4-P5)是
sqrt(0.499^2 + 0.93^2) = sqrt(0.249001 + 0.8649) = sqrt(1.113901) > 1
因此,您可以将5 点放入这样一个明显大于4 的< 矩形中。