編程之美:平面最近點對


一.概念引入

        最接近點對問題的提法是:給定平面上n個點,找其中的一對點,使得在n個點的所有點對中,該點對的距離最小。嚴格地說,最接近點對可能多於1對。為了簡單起見,這里只限於找其中的一對。

        最簡單的就是直接暴力,也可以分治,使用分治的話關鍵是如何合並,如果兩邊都是n/2個點比較的話,合並的時間是O(n^2),那么T(n)=2T(n/2)+O(n2),它的解為T(n)=O(n2),還是沒什么優勢,這就引導我們去優化合並算法。

        為了找到一個有效的合並算法,可以先考慮一維情形,看下圖:

0

        假設左右兩邊的最小距離是ans={ans1,ans2},很有可能最小距離分別存在於直線兩端p3、q3,如果真是這樣,則一定在p3∈(m-δ,m],q3∈(m,m+δ],且根據鴿巢原理,在這兩個半閉區間只有一個點,否則就違背了ans的定義(兩邊存在更小距離),關鍵是選好划分點,最壞T(n)=T(n-1)+O(n),它的解是T(n)=O(n2),這種效率降低的現象可以通過適當選擇分割點m,使左右兩邊有大致相等個數的點。

        下面看二維情形:

0

        考慮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)的矩形,如下圖

0

        若矩形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;
//    }
//}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM