最邻近点对问题(Closest-Pair Problem):一维的分治解法详解

这是该系列的第一篇。二维、三维的最邻近点对问题(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}\} {pn/2,pn/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>3n3

符合主定理情形一,总消耗是 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):二维的分治解法详解


写在最后:

  1. 该问题系列的所有实现代码、暴力代码以及对拍/测试代码(Java):戳这里
  2. 本人初来乍到,这是第一次在CSDN分享博文,如有谬误欢迎指出。
  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是一维接近对的分治法Java实现: ```java import java.util.*; class Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } public class ClosestPair { public static double findClosestPair(Point[] points) { Arrays.sort(points, (p1, p2) -> p1.x - p2.x); return findClosestPair(points, 0, points.length - 1); } private static double findClosestPair(Point[] points, int left, int right) { if (left >= right) { return Double.POSITIVE_INFINITY; } int mid = left + (right - left) / 2; double d1 = findClosestPair(points, left, mid); double d2 = findClosestPair(points, mid + 1, right); double d = Math.min(d1, d2); List<Point> strip = new ArrayList<>(); for (int i = left; i <= right; i++) { if (Math.abs(points[i].x - points[mid].x) < d) { strip.add(points[i]); } } Collections.sort(strip, (p1, p2) -> p1.y - p2.y); for (int i = 0; i < strip.size() - 1; i++) { for (int j = i + 1; j < strip.size() && strip.get(j).y - strip.get(i).y < d; j++) { double dist = distance(strip.get(i), strip.get(j)); if (dist < d) { d = dist; } } } return d; } private static double distance(Point p1, Point p2) { return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); } public static void main(String[] args) { Point[] points = {new Point(1, 3), new Point(2, 5), new Point(4, 6), new Point(7, 9), new Point(8, 2)}; System.out.println(findClosestPair(points)); // expected output: 1.4142135623730951 } } ``` 在此实现中,我们首先按照的x坐标进行排序。然后我们使用递归的方式将对分成两半,分别求出左边和右边的最接近对距离d1和d2。接下来,我们选取中间位置的mid,并找出所有x坐标与mid的x坐标相差小于d的。我们将这些按照y坐标进行排序,然后在其中查找距离最近的对。如果找到更小的距离,则更新d。最后,我们返回d作为结果。 时间复杂度:O(nlogn) 空间复杂度:O(n)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值