最近二维点对

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhufenghao/article/details/70860146

题目

给定二维坐标系中的 n 个点,求其中最近的点对。

  • 测试输入
    5
    1 2
    -2 3
    2 -1
    0 3
    -3 0
  • 测试输出
    1.414

分析

如果将点的距离两两计算出来需要 O(n2) 的时间复杂度,显然不是最优方案。先考虑一维数组情况,如果数组已经排好序,则只需要再遍历一遍,找到相邻值的最小距离即可,时间复杂度只是 O(nlog(n)) ,但是这种方法不能推广到二维情况。因为按照一个维度排序后,相邻点的最小距离并不是所有距离中的最小值。

这里可以采用分治法进行改进,首先将点集按照横坐标排序,根据其中心点将点集一分为二,构成最小距离的两个点要么同时在左边的子点集,要么同时在右边的子点集,或者一个在左边一个在右边。前两种情况可以递归解决,这里主要考察第三种情况。

假设中心点坐标为 (xmid,ymid) ,左边子点集最小距离为 δleft ,而右边最小距离为 δright ,那么点集的可能最小距离为 δ=min(δleft,δright) 。对于第三种情况,最小距离的点只能出现在 x=xmidδ x=xmid+δ 的纵向带状区域中。对于左边带状区域中的一个点 (xleft,yright) ,和它的距离可能小于 δ 的右边带状区域点只能出现在 y=yleftδ y=yleft+δ 横向带状区域中,这样就把待比较的右边点限制在两个正方形区域内了。这样对于每个左边点,只需要比较对应的两个正方形区域内的右边点就可以了,而不需要将纵向带状区域中所有点都比较一遍,从而大大降低了比较次数。具体做法是将右边带状区域中的点按照纵坐标排序,利用二分查找得到纵坐标最接近 yleft 的点,分别向上和向下找 δ 距离,从中检测是否还有与点 (xleft,yright) 距离小于 δ 的点。总体来看,利用这样分治的思想,算法的时间复杂度是 O(nlog(n))

这里写图片描述

进一步分析可知,两个正方形区域中最多只能有6个点,分别在各个顶点上。这是因为如果再有第7个点,那么必然在正方形内部区域中,使得与顶点的距离小于 δ ,产生矛盾。所以另一种简单做法是对每个左边点比较它上下各3个点即可。

代码

import java.util.*;

public class MinDistPair {
    static double dist(double[] a, double[] b) {
        double difx = a[0] - b[0];
        double dify = a[1] - b[1];
        return Math.sqrt(difx * difx + dify * dify);
    }

    static double distMin3(double[] a, double[] b, double[] c) {
        double distab = dist(a, b);
        double distac = dist(a, c);
        double distbc = dist(b, c);
        return Math.min(distab, Math.min(distac, distbc));
    }

    static double[][] getLeft(double[][] points, double bound, int mid, int lo) {
        int left = mid;
        double leftMin = points[mid][0] - bound;// 横坐标的左边界
        while (left - 1 >= lo && points[left - 1][0] > leftMin) --left;
        int len = mid - left;// 不包含mid点
        double[][] leftPoints = new double[len][2];
        System.arraycopy(points, left, leftPoints, 0, len);
        return leftPoints;
    }

    static double[][] getRight(double[][] points, double bound, int mid, int hi) {
        int right = mid;
        double rightMax = points[mid][0] + bound;// 横坐标的右边界
        while (right + 1 <= hi && points[right + 1][0] < rightMax) ++right;
        int len = right - mid + 1;// 包含mid点
        double[][] rightPoints = new double[len][2];
        System.arraycopy(points, mid, rightPoints, 0, len);
        return rightPoints;
    }

    static double minPair(double[][] points, int lo, int hi) {
        if (lo == hi - 1)// 递归到只有两个点时直接计算距离
            return dist(points[lo], points[hi]);
        if (lo == hi - 2)// 递归到只有三个点时不能再分,也直接计算距离
            return distMin3(points[lo], points[lo + 1], points[hi]);
        int mid = lo + (hi - lo) / 2;
        double curMin = Math.min(minPair(points, lo, mid), minPair(points, mid, hi));
        double[][] left = getLeft(points, curMin, mid, lo);// 找和mid点横坐标距离小于curMin的左边点
        double[][] right = getRight(points, curMin, mid, hi);// 找和mid点横坐标距离小于curMin的右边点
        Arrays.sort(right, new Comparator<double[]>() {
            @Override
            public int compare(double[] o1, double[] o2) {
                return (int) (o1[1] - o2[1]);// 按照纵坐标升序排列
            }
        });
        for (double[] lp : left) {// 每个左边点在右边点中比较
            int rmid = binarySearch(right, lp[1]);// 找到纵坐标距离最近的右边点
            int i = rmid - 1;
            double downMin = lp[1] - curMin;// 纵坐标的下边界
            while (i >= 0 && i < right.length && right[i][1] > downMin) {
                double tmpDist = dist(right[i], lp);
                if (tmpDist < curMin) curMin = tmpDist;
                --i;
            }
            int j = rmid;
            double upMax = lp[1] + curMin;// 纵坐标的上边界
            while (j >= 0 && j < right.length && right[j][1] < upMax) {
                double tmpDist = dist(right[j], lp);
                if (tmpDist < curMin) curMin = tmpDist;
                ++j;
            }
        }
        return curMin;
    }

    static int binarySearch(double[][] points, double key) {
        int i = 0;
        int j = points.length - 1;
        int mid;
        while (i <= j) {
            mid = i + (j - i) / 2;
            if (key == points[mid][1]) {
                return mid;
            } else if (key < points[mid][1]) {
                j = mid - 1;
            } else {
                i = mid + 1;
            }
        }
        return i;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        double[][] points = new double[n][2];
        for (int i = 0; i < n; i++) {
            points[i][0] = sc.nextDouble();
            points[i][1] = sc.nextDouble();
        }
        Arrays.sort(points, new Comparator<double[]>() {
            @Override
            public int compare(double[] o1, double[] o2) {
                return (int) (o1[0] - o2[0]);// 按照横坐标升序排列
            }
        });
        System.out.println(minPair(points, 0, n - 1));
    }
}

猜你喜欢

转载自blog.csdn.net/zhufenghao/article/details/70860146