1. 引言

在本文中,我们将展示如何找出一组二维点中曼哈顿距离最小的那对点。

2. 曼哈顿距离简介

假设我们有一组二维点:

$$ Z = {z_i}_{i=1}^{n}, z_i = (x_i, y_i), i = 1, 2, \ldots, n $$

我们希望找到曼哈顿距离(Manhattan Distance)最小的那对点。

曼哈顿距离是指只能沿水平和垂直方向移动时,两点之间的最短路径长度。如下图所示:

Manhattan Distance: example

其计算公式为:

$$ d_{M} \left( (x_i, y_i), (x_j, y_j)\right) = |y_j - y_i| + |x_j - x_i| $$

3. 算法设计

最直接的做法是暴力枚举所有点对,计算每对之间的曼哈顿距离,然后找出最小值。但这种方法的时间复杂度是 $O(n^2)$,效率较低。

为了提升性能,我们可以使用 分治策略(Divide and Conquer) 来解决这个问题。

3.1 分治策略思路

我们将点集 $Z$ 用一条垂直线分割成左右两部分 $P$ 和 $Q$:

Splitting the space in half

那么,最小曼哈顿距离的点对可能出现在以下三种情况之一:

  • 两点都在 $P$ 中
  • 两点都在 $Q$ 中
  • 一个点在 $P$,另一个点在 $Q$

我们递归地求解 $P$ 和 $Q$ 中的最小距离,分别记为 $\delta_P$ 和 $\delta_Q$,取 $\delta = \min(\delta_P, \delta_Q)$。

接着,我们只需要检查那些横跨分割线、且距离小于 $\delta$ 的点对。

我们只需要关注位于分割线两侧 $\delta$ 范围内的点,这些点组成的区域称为 strip(条带)

Strip

在这个 strip 中,我们检查每对点的距离,找出最小值 $\delta'$,最终的最小距离就是 $\min(\delta, \delta')$。

3.2 时间复杂度分析

设 $T(n)$ 为处理 $n$ 个点所需的时间,$f(n)$ 表示划分点集和查找 strip 中最小距离的时间。

则有递推式:

$$ T(n) = 2 T \left( \frac{n}{2} \right) + f(n) $$

如果 $f(n) = O(n)$,则总时间复杂度为 $O(n \log n)$。

但如果我们在 strip 中暴力枚举所有点对,则 $f(n) = O(n^2)$,整体时间复杂度退化为 $O(n^2)$,与暴力法无异。

因此,我们关键要做的,是在 strip 中快速找出最小距离

3.3 Strip 的结构特性

在 strip 中,我们只关注那些横跨分割线的点对。设当前最小距离为 $\delta$,则在 strip 中,点对之间的垂直距离必须小于 $\delta$。

我们可以在 strip 中将点按 $y$ 坐标排序,然后对于每个点,只需要检查其后最多 9 个点即可:

Rectangle filled

这样,我们可以在 $O(n)$ 时间内完成 strip 中的最小距离查找。

3.4 预排序优化

为了在递归中快速获取 strip 中的点并按 $y$ 排序,我们可以在最开始就对点集进行预排序:

  • 一份按 $x$ 坐标排序(用于划分点集)
  • 一份按 $y$ 坐标排序(用于 strip 中快速查找)

每次递归时,我们通过线性时间的合并操作(类似归并排序)来拆分排序数组,这样可以保证分治过程的时间复杂度仍为 $O(n \log n)$。

3.5 算法伪代码

algorithm FindClosest(Z, X, Y):
    // Z: 原始点集
    // X: 按 x 排序的点集
    // Y: 按 y 排序的点集
    // 返回最小曼哈顿距离和对应的点对

    if n <= 1:
        return ∞, null

    if n <= 3:
        // 手动计算所有点对
        return bruteForceFindClosest(Z)

    P, Q <- split Z into left and right halves by x
    X_P, X_Q <- split X into P and Q
    Y_P, Y_Q <- split Y into P and Q

    delta_P, pair_P <- FindClosest(P, X_P, Y_P)
    delta_Q, pair_Q <- FindClosest(Q, X_Q, Y_Q)

    delta <- min(delta_P, delta_Q)
    best_pair <- delta_P < delta_Q ? pair_P : pair_Q

    strip <- filter Y to keep only points within delta of the dividing line

    for each point p in strip:
        for next 9 points q after p:
            d <- manhattan(p, q)
            if d < delta:
                delta <- d
                best_pair <- (p, q)

    return delta, best_pair

3.6 曼哈顿距离的特殊性

我们之所以能在 strip 中只检查每个点后最多 9 个点,是因为曼哈顿距离的几何特性决定了在一个 $2\delta \times \delta$ 的矩形中最多只能容纳 6 个点(每侧最多 3 个)。

如果我们使用的是欧几里得距离(Euclidean Distance),则最多可以容纳 8 个点,因此需要检查最多 7 个后续点。

The Euclidean Rectangle

所以,不同距离函数会影响 strip 中点对的检查次数,但整体分治结构不变。

4. 总结

本文介绍了如何使用分治策略在 $O(n \log n)$ 时间内找出一组二维点中曼哈顿距离最小的那对点。

核心要点包括:

✅ 使用分治策略减少时间复杂度
✅ 利用 strip 结构优化跨区域点对查找
✅ 预排序和线性时间拆分确保整体效率
✅ 曼哈顿距离的几何特性决定了 strip 中最多只需检查每个点后 9 个点

这种算法是计算几何中的经典问题之一,具有广泛的应用价值。


原始标题:How to Find the Two Points With the Minimal Manhattan Distance