分治法在求解“最近对”问题中的应用(JAVA)

分治法在求解“最近对”问题中的应用

最近对问题在蛮力法中有过讲解,时间复杂度为O(n^2),下面将会采用分治法讲解这类问题,时间复杂度会降到O(nlogn)

我们将笛卡尔平面上n>1个点构成的集合称为P。若2<= n <= 3时,我们1可以通过蛮力法求解。但当n>3时,采用分治法或许是个更好的选择。假设这些点是按照x轴、y轴升序排列的,可以找出点集在x轴方向上的中位数m,做一条垂直x轴的分割线,由此点将点集划分为左右两个大小为n/2的子集P1和P2,之后通过递归求解出在子集中的最近对距离d1,d2,最后找出d=max{d1,d2}。

但是!!!不巧的是,我们忽略了一个问题,如果距离最近的两个点刚好分别在两个子集中,那么d就不是所有点对的最小距离。我们需要在每次合并子问题结果时,要加以判断是否存在这样的点对。方法是:只考虑以分割线为对称轴、宽度为2d的垂直带中的的点,因为其他点对的距离都是大于d的。

这里给出一个优化,当我们在垂直带中找到一个点p,只需要考虑p之后的5个点即可。

这是因为:如果我们在垂直带中找到p-p'两点的距离小于p,由于我们的序列时经过排序的,所以p'一定在p之后,且两点在y轴上的距离一定是小于d的(根据勾股定理,两点之间的距离如果小于d,那么x轴分量和y轴分量都是小于d的,反之,不可能存在这个点)。所以在几何学上,p'的位置一定在下图中的淡黄色矩形区域。而矩形区域内一般只能包含少量的候选点,这个数量最大为6(根据鸽巢定理)。图中6个红色点为极端的临界情况。我们将d * 2d的矩形划分为d/2 * 2d/3的6块区域,如果超过6个点,假设为7,那么一定会出现某个小矩形中有两个点,这两个点的最大距离为图中红线距离5/6d <d,这和d的意义不符。


import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

class Point {
    double x;
    double y;
    Point (double x, double y) {
        this.x = x;
        this.y = y;
    }
}
public class Main {
    static Point[] point;
    static Point[] minP = new Point[2];
    static Scanner in = new Scanner(System.in);
    public static void main(String[] args) {
        int n = in.nextInt();
        point = new Point[n];
//        for (int i = 0; i < n; i++) {
//            int a = in.nextInt();
//            int b = in.nextInt();
//            point[i] = new Point(a, b);
//        }
        point[0] = new Point(1,3);
        point[1] = new Point(2,1);
        point[2] = new Point(3,5);
        point[3] = new Point(4,4);
        point[4] = new Point(5,2);
        Arrays.sort(point,0, n, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return (int) (o1.x - o2.x);
            }
        });
        System.out.println(point.length);
        double minD = closestPoint(0, point.length-1);
        for (int i = 0; i < 2; i++) {
            System.out.println(minP[i].x + "," + minP[i].y);
        }
        System.out.println(minD);
    }

    private static double closestPoint(int low, int high) {
        Point[] temp1 = new Point[2];
        Point[] temp2 = new Point[2];
        Point[] p = new Point[high - low + 1];
        double d, d1, d2, d3;
        int index = 0;
        if (high - low == 1) {
            minP[0] = new Point(point[low].x, point[low].y);
            minP[1] = new Point(point[high].x, point[high].y);
            return distance(point[low], point[high]);
        }
        if (high - low == 2) {
            d1 = distance(point[low], point[low+1]);
            d2 = distance(point[low+1], point[high]);
            d3 = distance(point[low], point[high]);
            if ((d1 <= d2) && (d1 <= d3)) {
                minP[0] = new Point(point[low].x, point[low].y);
                minP[1] = new Point(point[low+1].x, point[low+1].y);
                return d1;
            } else if (d2 <= d3) {
                minP[0] = new Point(point[low+1].x, point[low+1].y);
                minP[1] = new Point(point[high].x, point[high].y);
                return d2;
            } else {
                minP[0] = new Point(point[low].x, point[low].y);
                minP[1] = new Point(point[high].x, point[high].y);
                return d3;
            }
        }
        int mid = (low + high) / 2;
        d1 = closestPoint(low, mid);
        temp1[0] = minP[0];
        temp1[1] = minP[1];
        d2 = closestPoint(mid+ 1, high);
        temp2[0] = minP[0];
        temp2[1] = minP[1];
        if (d1 < d2) {
            d = d1;
            minP[0] = temp1[0];
            minP[1] = temp1[1];
        } else {
            d = d2;
            minP[0] = temp2[0];
            minP[1] = temp2[1];
        }
        for (int i = mid;i>=low && (point[mid].x - point[i].x) < d; i--) {
            p[index++] = point[i];
        }
        for (int i = mid+1;i<=high && (point[i].x - point[mid].x) < d; i++) {
            p[index++] = point[i];
        }
        Arrays.sort(p, 0, index, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return (int) (o1.y - o2.y);
            }
        });
        for (int i = 0; i < index-1; i++) {
            for (int j = i+1; j < index; j++) {
                if ((p[j].y - p[i].y) >= d) {
                    break;
                } else {
                    d3 = distance(p[i], p[j]);
                    if (d3 < d) {
                        minP[0] = new Point(p[i].x, p[i].y);
                        minP[1] = new Point(p[j].x, p[j].y);
                    }
                }
            }
        }
        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));
    }
}

Input:

5

Output:

4.0,4.0
3.0,5.0
1.4142135623730951

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值