最近二维点对

来源:互联网 发布:客单价方面的优化 编辑:程序博客网 时间:2024/05/18 01:28

题目

给定二维坐标系中的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));    }}
0 0
原创粉丝点击