📜  最近的一对点 | O(nlogn) 实现(1)

📅  最后修改于: 2023-12-03 15:10:37.330000             🧑  作者: Mango

最近的一对点 | O(nlogn) 实现

最近的一对点(Closest Pair Problem)是计算几何中的一个经典问题,给定平面上的 $n$ 个点,找出其中距离最近的一对点。这个问题可以用 $O(n^2)$ 的暴力算法解决,但我们可以使用分治思想设计一个 $O(n\log n)$ 算法。

算法思路

假设我们已经将所有点按照横坐标排序并分成了两个部分 $L$ 和 $R$,那么最近的一对点要么在 $L$ 中,要么在 $R$ 中,要么横跨两个部分。我们可以分别找出 $L$ 和 $R$ 中的最近点对,然后再考虑横跨两个部分的情况。设两个距离最近的点分别在 $L$ 和 $R$ 中,它们的距离为 $d$。显然,横跨两个部分的最近点对要么在竖直线 $x=\mathrm{mid}$ 左侧或右侧,要么跨越竖直线 $x=\mathrm{mid}$。

我们可以将竖直线 $x=\mathrm{mid}$ 左侧距离竖直线 $x=\mathrm{mid}$ 的距离不超过 $d$ 的点加入一个单独的数组 $S$ 中,然后按照纵坐标排序。接下来,我们从 $S$ 中每个点出发,与 $S$ 中之后的若干个点计算距离。显然,每个点只需要计算与距离之差不超过 $d$ 的后续点即可。由于 $S$ 中最多只有 $6$ 个点在任意一个区间内,所以每个区间的复杂度为 $O(1)$。在所有的区间中选取距离最近的一对点即可。

代码实现

下面是最近点对问题的 $O(n\log n)$ 实现的代码片段,使用了 Python 语言:

from math import sqrt

def dist(p, q):
    return sqrt((p[0]-q[0])**2 + (p[1]-q[1])**2)

def closest_pair(L, R):
    if len(L) < 2:
        return float('inf')
    elif len(L) == 2:
        return dist(L[0], L[1])
    else:
        mid = (L[-1][0] + R[0][0]) // 2
        dl = closest_pair(L, [p for p in R if p[0] < mid])
        dr = closest_pair([p for p in L if p[0] >= mid], R)
        d = min(dl, dr)
        S = [p for p in L if mid - p[0] <= d] + [p for p in R if p[0] - mid <= d]
        S.sort(key=lambda p: p[1])
        for i in range(len(S)):
            for j in range(i+1, len(S)):
                if S[j][1] - S[i][1] > d:
                    break
                d = min(d, dist(S[i], S[j]))
        return d

上面的代码先定义了一个计算距离的函数 dist(p, q),然后定义了一个递归函数 closest_pair(L, R),其中 LR 分别表示点集左半部分和右半部分。如果 L 中只有一个点,我们返回正无穷大。如果 L 中有两个点,我们直接计算它们的距离。否则,我们计算 LR 中的最近点对距离,然后获取距离竖直线 $x=\mathrm{mid}$ 不超过 $d$ 的点到 $S$ 中,并按照纵坐标排序。最后,我们枚举 $S$ 中的点对,找到最近的一对点对距离。

总结

最近点对问题是计算几何中的一个经典问题,它可以使用分治思想设计一个时间复杂度为 $O(n\log n)$ 的算法。这个算法的实现依赖于两个关键的步骤:计算横跨 $L$ 和 $R$ 的最近点对距离,以及计算横跨竖直线 $x=\mathrm{mid}$ 的最近点对距离。需要注意的是,在计算横跨竖直线 $x=\mathrm{mid}$ 的最近点对距离时,我们需要对 $S$ 中的点按照纵坐标排序,以保证时间复杂度为 $O(n)$。