1. 程式人生 > >最近點對演算法分析Closest Pair of Points

最近點對演算法分析Closest Pair of Points

Given n points in the plane, find a pair with smallest Euclidean distance between them.
題目很簡單,就是在二維平面上尋找到距離最近的點對。

最直接的演算法就是暴力尋找法。一個一個找唄,複雜度顯然是O(n^2)。
    public static int minDis(Point[] Ps,int start,int end){
        //暴力找出最小距離
        if(end-start+1<2){
            return 0;
        }
        int
min=calculateDistance(Ps[start],Ps[end]); int temp=min; for(int i=start;i<end;i++){ for(int j=i+1;j<=end;j++){ temp=calculateDistance(Ps[i],Ps[j]); if(temp<=min){ min=temp; } } } return
min; }

還是那個問題,我們能做得更好嗎?顯然是可以的。下面我們將用分治法將它降到O(nlog(n));

運用分治法:要找到整個平面的最近點對,我們可以先找左右兩邊最近的點對:

這裡寫圖片描述

那麼這條中線如何確定呢?顯然可以先按照x軸的座標進行排序,這裡排序的複雜度是O(nlog(n)),還是可以接受的。
因為我剛剛寫了求無序陣列的中位數的方法,複雜度可以降到線性,這裡可以直接拿來用:

    private static void swap(Point[] Ps,int i,int j){
        Point temp=Ps[i];
        Ps[i]=Ps[j];
        Ps[j]=temp;
    }

    private
static int findMiddle(Point[] Ps,int start,int end){ //線性時間找出從start到end的元素x座標的中位數 if(Ps==null){ return 0; } if(Ps.length==1){ return Ps[0].x; } int key=Ps[end].x;//基準 int k=start; for(int i=start;i<end;i++){ if(Ps[i].x<=key){ swap(Ps,k++,i); } } swap(Ps,k,end); //只找一邊 T(n)=T(n/2)+O(n); int median=start+(end-start+1)/2;//中點 if(k>median){ return findMiddle(Ps,start,k-1); }else if(k<median){ return findMiddle(Ps,k+1,end); }else{ return median;//返回中位數的下標,此時陣列中元素的次序已被改變 } } //以上完成後中位數在中間

此時返回左邊的最小距離dl和右邊的最小距離dr;設d為dl和dr的較小值。

找到中間數垂線後就結束了嗎?顯然還會存在兩個點在兩邊的最近點。

這裡寫圖片描述

那是不是要左右兩邊分別遍歷一遍呢?那又太費時間了。可以看到,如果這個點的x座標和中間數的差值大於d,那麼就不用考慮這個點了。也就是說我們僅僅需要考慮如下中間的帶狀區域裡的點:

這裡寫圖片描述

然後對帶狀區域裡面的點進行暴力尋找似乎就完成任務了,但是我們多想一步,就和超出帶狀區域的點我們捨棄一樣,我們能否再橫著畫一條帶狀區域呢?這樣就需要對帶狀區域裡的點根據y座標進行排序。  
我們可以推知如果存在這樣的點對,那它一定在如下的d*(2d)的矩形框內。

這裡寫圖片描述

而在這兩個d*d的正方向框裡,最多都只能含有4個點,如果多於4個將肯定會出現距離小於d的點對!因此對於帶狀區域裡的每個點,最多隻需要檢查7個附近點就可以了,這一步的複雜度將是常量級的!

參考了其他大神的程式碼後,我發現為了避免對y排序,可以以中線為界向兩邊掃描,這樣更具有技巧性。

最後貼上找最近點對的Java程式碼:
    public static int calculateDistance(Point p1,Point p2){
        return (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y);
        //先返回距離的平方
    }

    public static int findmin(Point[] Ps,int start,int end){
        //找從start到end的元素中的最小距離對
        if(Ps.length==1){
            return 0;
        }
        if(Ps.length==2){
            return calculateDistance(Ps[0], Ps[1]);
        }

        if(Ps.length==3 || end-start+1<=3){
            //直接找最小距離對
            int temp1=calculateDistance(Ps[start],Ps[start+1]);
            int temp2=calculateDistance(Ps[start],Ps[start+2]);
            int temp3=calculateDistance(Ps[start+1],Ps[start+2]);
            temp1=temp1<temp2?temp1:temp2;
            temp1=temp1<temp3?temp1:temp3;          
            return temp1;
        }

        //根據x座標找出中位數垂線
        int median=findMiddle(Ps,start,end);
        int dl=findmin(Ps,start,median);//左邊
        int dr=findmin(Ps,median,end);//右邊

        int d=dl<dr?dl:dr;

        int temp=d;
        for (int i = median; i>=start && Ps[median].x-Ps[i].x<d ; i--) {//在左邊的帶狀區域裡面
            for(int j=median+1;j<=end && Ps[j].x-Ps[median].x<d ;j++){//在右邊的帶狀區域裡面
                if(Math.abs(Ps[j].y-Ps[i].y)<d){//縱座標之差小於d
                    temp=calculateDistance(Ps[i],Ps[j]);                                        
                    if(temp<=d)
                        d=temp;                                         
                }
            }           
        }       
        return d;
    }