这是该系列的第一篇。二维、三维的最邻近点对问题(Closest-Pair Problem):戳这里
1 问题描述
一维下,每个点即是一个数,所有点线性分布于x轴上,如图所示:
很明显,最近点对一定是相邻的两个点,所以暴力解法就是依次检查 p i , p i + 1 p_i, p_{i+1} pi,pi+1,最后返回距离最小的点对即可。暴力算法的时间复杂度是 O ( n ) O(n) O(n)。
其实,可以发现最邻近点对问题非常适合拆分成子问题,利用分治思想可以把这一步的时间复杂度降到 O ( l o g n ) O(logn) O(logn)。
2 算法描述
Divide步骤:
对于任意的点集 P P P,其中点的数量 ∣ P ∣ = n \vert P\vert = n ∣P∣=n,那么可以在第 ⌊ n / 2 ⌋ \lfloor n/2\rfloor ⌊n/2⌋个点的坐标(记为 x ∗ x^* x∗)处,将其分为 Q Q Q区与 R R R区。(实际中,如果P是有序的,那么只需要传递区间索引即可,故它是 O ( 1 ) O(1) O(1)的)
现在原问题就变成了两个完全相同的子问题。最终只有小于等于3个点时(或其他小常数,重要的是消耗是常数时间的),可以直接比较每个点对得出答案。
Merge步骤:
假设 Q Q Q区找到了局部最邻近点 { q 1 , q 2 } \{q_1, q_2\} {q1,q2},距离为 δ 1 \delta_1 δ1,同理 R R R区找到了 { r 1 , r 2 } \{r_1, r_2\} {r1,r2},距离为 δ 2 \delta_2 δ2,对于原问题的解,有3种情况:
- 处于分割处的点对,即 { p ⌊ n / 2 ⌋ , p ⌊ n / 2 ⌋ + 1 } \{p_{\lfloor n/2\rfloor}, p_{\lfloor n/2\rfloor + 1}\} {p⌊n/2⌋,p⌊n/2⌋+1},所对应的距离 δ 3 \delta_3 δ3是最小的,应返回这个点对。
- Q Q Q区中找到的点对距离最小,应返回 { q 1 , q 2 } \{q_1, q_2\} {q1,q2}。
- R R R区中找到的点对距离最小,应返回 { r 1 , r 2 } \{r_1, r_2\} {r1,r2}。
现在Merge步骤就完成了。
3 算法分析
Divide与Merge步骤都是 O ( 1 ) O(1) O(1),易知消耗的递推公式为:
f n = { 2 f n / 2 + O ( 1 ) n > 3 O ( 1 ) n ≤ 3 f_n=\left\{ \begin{aligned} &2f_{n/2} + O(1) & n>3\\ &O(1) & n\le 3 \end{aligned} \right. fn={2fn/2+O(1)O(1)n>3n≤3
符合主定理情形一,总消耗是 O ( n ) O(n) O(n)的。
果真如此吗?
以上的所有分析有一个重要的前提,在运行这个算法(包括暴力法)之前,都需要对点进行排序,而这一步是 O ( n l o g n ) O(nlogn) O(nlogn)的。不过,一维下Divide和Merge步骤中所用的思想,与二维、三维情况下是高度相似的。这里用分治解决一维情况,对二三维的理解是至关重要的。
4 伪代码
这里贴上伪码,重在理解。源码请移步 文末链接 下一节。
5 Java实现
(算了,我发现手机上我自己都不会去点连接看源码,还是放这里了好了,长就长点)
_(:з」∠)_
Dim1.java
package solve;
import java.util.Arrays;
public class Dim1 {
private Long[] points;
/**
* Get distance of two points.
* Give the index of two points
*/
private long getDis(int idx1, int idx2) {
return Math.abs(points[idx2] - points[idx1]);
}
/**
* Solve the problem
* @return the point pair with smallest distance
*/
public Long[] solve(Long[] points) {
this.points = points;
Arrays.sort(points);
int[] res = find(0, points.length - 1);
return new Long[]{points[res[0]], points[res[1]]};
}
/**
* Find the pair with smallest distance
* @param lo the lower bound for searching
* @param hi the upper bound for searching
* @return the index of the point pair
*/
private int[] find(int lo, int hi) {
switch (hi - lo + 1) {
case 2:
return new int[]{lo, hi};
case 3:
if (getDis(lo, lo + 1) <= getDis(lo + 1, lo + 2)) {
return new int[]{lo, lo + 1};
} else {
return new int[]{lo + 1, lo + 2};
}
}
int mi = (lo + hi) / 2;
int[] left = find(lo, mi);
int[] right = find(mi + 1, hi);
long dis1 = getDis(left[0], left[1]);
long dis2 = getDis(mi, mi + 1);
long dis3 = getDis(right[0], right[1]);
if (dis1 <= dis2 && dis1 <= dis3) {
return left;
} else if (dis2 <= dis1 && dis2 <= dis3) {
return new int[]{mi, mi + 1};
} else {
return right;
}
}
}
下一篇:最邻近点对问题(Closest-Pair Problem):二维的分治解法详解
写在最后:
- 该问题系列的所有实现代码、暴力代码以及对拍/测试代码(Java):戳这里
- 本人初来乍到,这是第一次在CSDN分享博文,如有谬误欢迎指出。