Skip to content

几何

Closest Pair

平面上两个点 ,其欧几里得距离是 Closest Pair 问题是给定 个点 ,找到一对点 其欧几里得距离 最小。

暴力法就是遍历每一对点 ,时间复杂度是 。有更好的算法吗?

首先考虑一维的情况,那么 的欧氏距离就是 。我们可以先排序,时间复杂度为 ,然后线性扫描排序的点,找到距离最小的,第二步时间复杂为 ,因此算法时间复杂度是

现在回到二维情况。类似之前的数据预处理,我们准备两份数据集,分别按照 轴排序,得到 。数据预处理的复杂度是 。但是和之前不同的是,最小距离的一对点可能不是 中连续的两个点。如下图所示。

下面尝试分治法。将 平分成两个部分。对左右两个部分分别递归调用。然后调用一个函数处理两个点分别在两边的情形。下面是伪码

if nPoints <= 3:
    brute-force search

Lx = first half of Px, sorted by x-coordinate
Ly = first half of Px, sorted by y-coordinate
Rx = second half of Px, sorted by x-coordinate
Ry = second half of Px, sorted by y-coordinate

(l1, l2) := ClosestPair(Lx, Ly) // best left pair
(r1, r2) := ClosestPair(Rx, Ry) // best right pair
(s1, s2) := ClosestSplitPair(Px, Py) // best split pair

return best of (l1, l2), (r1, r2), (s1, s2)
Lx Rx 时间复杂度。Ly Ry 可以扫描 ,根据 坐标分到其中一个集合中, 的时间复杂度。

如果 ClosestSplitPair 能在 时间复杂度完成,每次递归规模减少一半,那么整个算法的时间复杂度就是

稍微调整一下上面的算法,在知道左右两边最小距离后,将这个值传给 ClosestSplitPair,那么这个函数仅需要处理比这个最小值还小的点对即可。

下面是 ClosestSplitPair 的伪码,其中 delta 是左右两边的最小距离。

x_mid = largest x-coordinate in left half // median x-coordinate
Sy := q1, q2,...,q_l with x-coordinate between x_mid + delta and x_mid - delta, sorted by y-coordinate
best = delta
bestPair = NULL
for i = 1 to (l - 1):
    for j = 1 to min(7, l - i):
        if d(q_i, q_j) < best:
            best = d(q_i, q_j)
            bestPair = (q_i, q_j)

return bestPair
Sy 可以通过扫描 ,过滤掉 轴坐标不满足 x - deltax + delta 区间的值。这一步的时间复杂度是

后面两个 for 循环,但是第二层最多执行 7 次,因此时间复杂也是

因此算法的时间复杂度是 ,比暴力法 要快很多。下面讨论算法的正确性。

如果距离最小的对出现在左边或者右边,递归调用返回的,没问题。如果这对点分裂到了两边,如何证明正确性呢?

假定距离最小的点对是 ,并且 ,其中 是上面代码中的 x_midSy 包含的点对,如果一个在左边,一个在右边,那么包含了所有距离小于等于 (即代码中的 delta)的点对。而 ,因此 在备选集合 Sy 中。

更关键的问题是,我们从 开始最多只找 7 个点就得到了答案。这意味着 轴坐标介于 的点最多只能有六个,否则我们就会找不到正确答案。

我们画一个矩形,宽 ,然后等分成八个格子,如下图所示。不妨设 坐标更小,那么位于格子的底部。每个格子中最多只能有一个点。如果两个点在一个格子内,那么它们的距离小于 ,那么这一对点就是最短距离的点对了。包含 在内最多只需要计算八个点的距离,从 开始,最多找 7 个点就能找到最距离最短的点对。