编程之美:平面最近点对

一.概念引入

        最接近点对问题的提法是:给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。

        最简单的就是直接暴力,也可以分治,使用分治的话关键是如何合并,如果两边都是n/2个点比较的话,合并的时间是O(n^2),那么T(n)=2T(n/2)+O(n2),它的解为T(n)=O(n2),还是没什么优势,这就引导我们去优化合并算法。

        为了找到一个有效的合并算法,可以先考虑一维情形,看下图:

        假设左右两边的最小距离是ans={ans1,ans2},很有可能最小距离分别存在于直线两端p3、q3,如果真是这样,则一定在p3∈(m-δ,m],q3∈(m,m+δ],且根据鸽巢原理,在这两个半闭区间只有一个点,否则就违背了ans的定义(两边存在更小距离),关键是选好划分点,最坏T(n)=T(n-1)+O(n),它的解是T(n)=O(n2),这种效率降低的现象可以通过适当选择分割点m,使左右两边有大致相等个数的点。

        下面看二维情形:

        考虑P1中任意一点p,它若与P2中的点q构成最接近点对的候选者,则必有dis(p,q)<ans(图中的sigma)。满足这个条件的P2中的点有多少个呢?容易看出这样的点一定落在一个δ×2δ的矩形R中,由δ的意义可知P2中任何2个S中的点的距离都不小于δ。由此可以推出矩形R中最多只有6个S中的点。事实上,我们可以将矩形R的长为2δ的边3等分,将它的长为δ的边2等分,由此导出6个(δ/2)×(2δ/3)的矩形,如下图

        若矩形R中有多于6个S中的点,则由鸽舍原理易知至少有一个δ×2δ的小矩形中有2个以上S中的点。设u,v是这样2个点,它们位于同一小矩形中,则因此d(u,v)≤5δ/6<δ 。这与δ的意义相矛盾。也就是说矩形R中最多只有6个S中的点。图4(b)是矩形R中含有S中的6个点的极端情形。由于这种稀疏性质,对于P1中任一点p,P2中最多只有6个点与它构成最接近点对的候选者。因此,在分治法的合并步骤中,我们最多只需要检查6×n/2=3n对候选者,而不是n2/4对候选者。这是否就意味着我们可以在O(n)时间内完成分治法的合并步骤呢?现在还不能作出这个结论,因为我们只知道对于P1中每个S1中的点p最多只需要检查P2中的6个点,但是我们并不确切地知道要检查哪6个点。为了解决这个问题,我们可以将p和P2中所有S2的点投影到垂直线l上。由于能与p点一起构成最接近点对候选者的S2中点一定在矩形R中,所以它们在直线l上的投影点距p在l上投影点的距离小于δ。由上面的分析可知,这种投影点最多只有6个。因此,若将P1和P2中所有S的点按其y坐标排好序,则对P1中所有点p,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者,对P1中每一点最多只要检查P2中排好序的相继6个点。

        参考资料:http://blog.csdn.net/junerfsoft/article/details/2975495

二.算法Java实现

        以hdu1007为例,果断AC……

import java.util.*;
/*
 * x轴排序tle,y轴果断ac
 */
public class HDU1007 {

    public static void main(String[] args) {
        new DK().go();
    }
}

class Point implements Comparable<Point>{
    double x;
    double y;

    public Point() {
        this.x = 0;
        this.y = 0;
    }

    @Override
    public int compareTo(Point obj) {
        Point other = obj;
        if(this.y!=other.y) {//由小到大排序
            return (int)Math.ceil(this.y - other.y);
        }
        return (int)Math.ceil(this.x - other.x);
    }
}

class DK {

    double x;
    double y;
    Point point[];
    int a[];

    public void go() {
        Scanner sc = new Scanner(System.in);
        while(true) {
            int n = sc.nextInt();
            if(0==n) {
                break;
            }
            point = new Point[n];
            for(int i=0; i<n; i++) {
                point[i] = new Point();
            }
            for(int i=0; i<n; i++) {
                x = sc.nextDouble();
                y = sc.nextDouble();
                point[i].x = x;
                point[i].y = y;
            }
            Arrays.sort(point);
//            for(int i=0; i<n; i++) {
//                System.out.println(point[i].x+" "+point[i].y);
//            }
            a = new int[n];
            double ans = solve(0,n-1)/2;
            System.out.println(String.format("%.2f", ans));
        }
    }
    private double solve(int left, int right) {
        double ans = 1e-7;
        if(left==right) {
            return ans;
        }
        if(left==right-1) {
            return distance(point[left], point[right]);
        }
        int mid = (left+right)>>1;
        double ans1 = solve(left,mid);
        //注意:不是mid+1
        double ans2 = solve(mid,right);
        ans = Math.min(ans1,ans2);
        int j = 0;
        for(int i=left; i<=right; i++) {
            if(Math.abs(point[i].y-point[mid].y)<=ans) {
                a[j++] = i;
            }
        }
        /*
         * 加上下面的排序就AC,否则WA,我认为至多TLE,
         * 因为扫描的是和point[i]最相近的两个矩形2*ans区间
         */
        //不知道如何用comparator接口实现间接排序,所以就写了个选择排序
        mySort(a,j);
        for(int i=0; i<j; i++) {
            for(int k=i+1; k<j&&Math.abs(point[a[i]].x - point[a[k]].x)<ans; k++) {
                double dis = distance(a[i], a[k]);
                if(ans>dis) {
                    ans = dis;
                }
            }
        }
        return ans;
    }
    private void mySort(int[] a, int j) {
        for(int i=0; i<j; i++) {
            for(int k=i+1; k<j; k++) {
                if(point[a[i]].x<point[a[k]].x) {
                    int temp = a[i];
                    a[i] = a[k];
                    a[k] = temp;
                }
            }
        }
    }
    private double distance(Point p1, Point p2) {
        double dis = Math.hypot(p1.x-p2.x, p1.y-p2.y);
        return dis;
    }
    private double distance(int i, int j) {//point搞为成员变量
        double dis = Math.hypot(point[i].x-point[j].x, point[i].y-point[j].y);
        return dis;
    }
}
//class Com implements Comparator<Point> {
//
//    @Override
//    public int compare(Point o1, Point o2) {
//
//        return o1.y - o2.y;
//    }
//}
时间: 2024-11-09 03:56:39

编程之美:平面最近点对的相关文章

编程之美:找到符合条件的整数

一.问题描述 任意给定一个正整数N,求一个最小正整数M(M>1),使得N*M的十进制形式只含1和0. 比如 N=99,M=1 122 334 455 667 789 ,N*M=111 111 111 111 111 111; M就是当N=99时,符合条件的数 二.解题思路 考虑将问题转化为:找只含有0和1的能被N整除的最小正整数.可以看出这是和原问题等价的. 那么需要将0和1组成的所有数从小到大遍历吗? 这样的话,如果寻找的数是k位,则需要搜索2k-1个数才能得到结果. 这里采用的方式是在计算中

编程之美:1的数目

一.问题描述 给定一个十进制数N,写下从1开始,到N的所有整数,然后数一下之中所有"1"的个数. 例如: N=12,(1,2,3,4,5,6,7,8,9,10,11,12)共有5个1 二.解题思想 假设N=abcde为一个整数,a,b,c,d,e分别对应十进制数,如果要计算(1到N)百位出现1的个数,他将受三个因素的影响:百位以上的数,百位数和百位一下的数,具体依赖如下: 分别设整数N百位以上,百位和百位一下的数字分别为:preNum,curNum,proNum,如N=abcde的三个

编程之美:24点游戏

一.问题描述 给玩家4张牌,每张牌牌面值在1~13之间,允许其中有数值相同的牌.采用加.减.乘.除四则运算,允许中间运算存在小数,并且可以使用括号,但每张牌只能使用一次,尝试构造一种表达式,使其运算结果为24. 如 输入:3 3 7 7 输出:(((3)/(7))+(3))*(7) 二.程序实现原理 遍历所有可能的组合,直到找到一个符合条件的组合或者遍历所有情况没有找到满足的组合,具体详见代码注释 三.程序基本实现 #include<iostream> #include<string&g

编程之美:小飞的电梯调度算法

一.问题描述 亚洲微软研究院所在的希格玛大厦一共有6部电梯.在高峰时间,每层都有人上下,电梯每层都停.实习生小飞常常会被每层都停的电梯弄的很不耐烦,于是他提出了这样一个办法: 由于楼层并不算太高,那么在繁忙的上下班时间,每次电梯从一层往上走时,我们只允许电梯停在其中的某一层.所有乘客从一楼上电梯,到达某层后,电梯停下来,所有乘客再从这里爬楼梯到自己的目的层.在一楼的时候,每个乘客选择自己的目的层,电梯则计算出应停的楼层. 问:电梯停在哪一层楼,能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少?

盖茨扎克伯格现身说法:讲述编程之酷

新浪科技讯 北京时间3月2日上午消息,美国公益组织Code.org的一段最新视频请来了马克·扎克伯格(Mark Zuckerberg)和比尔·盖茨(Bill Gates)等业界大佬现身说法,讲述编程之酷.视频:盖茨扎克伯格等讲述编程之酷 媒体来源:新浪科技这段视频不仅展示了Facebook的前卫工作环境,还邀请了迈阿密热火队的克里斯·波什(Chris Bosh)和黑眼豆豆首脑人物Will.i.am讲述编程之美:程序员已经不再是西装革履的码农,他们可以穿着套头衫在Facebook园区内东奔西跑,而

iOS开发:多线程编程之NSThread的使用详解

  1.简介: 1.1 iOS有三种多线程编程的技术,分别是: 1..NSThread 2.Cocoa NSOperation (iOS多线程编程之NSOperation和NSOperationQueue的使用) 3.GCD 全称:Grand Central Dispatch( iOS多线程编程之Grand Central Dispatch(GCD)介绍和使用) 这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的. 这篇我们主要介绍和使用NSThr

Python中线程编程之threading模块的使用详解

  这篇文章主要介绍了Python中线程编程之threading模块的使用详解,由于GIL的存在,线程一直是Python编程中的焦点问题,需要的朋友可以参考下 threading.Thread Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入.下面分别举例说明.先来看看通过继承th

ruby元编程之method

  这篇文章主要介绍了ruby元编程之method_missing的一个使用细节,本文介绍在使用method_missing时造成死循环的一个现象,需要的朋友可以参考下 我们知道顶级域,定义域的self是啥? 代码如下: puts self #main puts self.class #Object 我们知道当一个方法被调用的时候,如果没有对象接受,默认就是self,如: 代码如下: def tell_me_who puts self end tell_me_who #main 方法调用是这样的

Node.js 异步编程之 Callback介绍(一)

 这篇文章主要介绍了Node.js 异步编程之 Callback介绍(一),本文用实例讲解Callback的相关知识,本文是第一篇,下一篇小编会跟进,需要的朋友可以参考下     Node.js 基于 JavaScript 引擎 v8,是单线程的.Node.js 采用了与通常 Web 上的 JavaScript 异步编程的方式来处理会造成阻塞的I/O操作.在 Node.js 中读取文件.访问数据库.网络请求等等都有可能是异步的.对于 Node.js 新人或者从其他语言背景迁移到 Node.js