分治的運用---最近點對

問題很簡單,就是在平面內有n個點,求出距離最近的一對點

我們定義點的數據類型爲

  struct point{
      double x;
      double y;
  };

最簡單最粗暴的方法就是枚舉任意連點間的距離,動態記錄最小的。

  point N[maxn];
  
  double dis(const point& a, const point& b){ //求兩點間距離
     double res= (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
     return sqrt(res);
  }
  .....
  //假設所有點都已經記錄在數組N中
  for(int i=0;i<n;i++){
      for(int j=i+1;j<n;j++){
          ans = min(ans, dis(N[i],N[j]) );
      }
  }
但是對於這樣方法,我們是並不滿意的,時間複雜度爲 O(n^2) 有點不能接受,於是我們接着分析平面上點的特性
我們可以很明顯的發現,兩點間的距離公式爲  ,

也不難發現影響距離的就是(x1-x2)和(y1-y2)

換句話說就是最近點對就是:橫座標差和縱座標差都相對較小的,如已知三個點 a(1,5), b(3,6), c(5,2),距離的小的一定是min(ab,bc),

所以我們對所有點按橫座標升序排一次序(橫座標相同的按縱座標升序),那麼最近點對就一定是相鄰的兩個點了,

在繼續分析不要滿足於掃描一遍求最近的距離,相鄰的兩個點,這不可以二分求解了麼,複雜度便又由O(n) 變爲O(logn) 了,

於是就有了下面的思路:

分別遞歸求出左半邊和右半邊的最短距離,然後合併

求解時我們分兩種情況

(1):點數小於等於三時:
                     

  (2):點數大於三

     先劃分集合S爲SL和SR,使得SL中的每一個點位於SR中每一個點的左邊,並且SL和SR中點數相同。分別在SL和SR中解決最近點對問題,得到DL和DR,分別表示SL和SR中的最近點對的距離。令d=min(DL,DR)。如果S中的最近點對(P1,P2)。P1、P2兩點一個在SL和一個在SR中,那麼P1和P2一定在以L爲中心的間隙內,以L-d和L+d爲界,如下圖所示:

                                    

於是呢結果不就顯而易見了

給一個完整的代碼(hdu1007):

#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn= 100005;
struct point{
    double x;
    double y;
}N[maxn];
int A[maxn];

double dis(const point& a, const point& b){
    double res= (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y);
    return sqrt(res);
}

bool cmpx(const point& a, const point& b){
    return a.x < b.x;
}

bool cmpy(int a, int b){
    return N[a].y < N[b].y;
}

double closestDis(int l, int r){
    if( (l+1) == r )
        return dis(N[l], N[r]);
    if( (l+2)== r )
        return min ( min( dis(N[l], N[l+1]), dis( N[l],N[r] ) ), dis(N[l+1],N[r] ) );
    int mid= (l+r)>>1;
    double ans= min( closestDis(l,mid), closestDis(mid+1, r) );
    int k=0;
    for(int i=l;i<=r;i++){
        if ( N[i].x >(N[mid].x -ans) && N[i].x <( N[mid].x + ans)  ){
            A[k++]= i;
        }
    }
    sort(A,A+k,cmpy);
    for(int i=0;i<k;i++){
        for(int j=i+1;j<k;j++){
            if( ( N[A[j]].y - N[A[i] ].y ) >=ans )
                break;
            ans = min(ans,dis(N[A[i]], N[A[j]]));
        }
    }
    return ans;
}

int main(){
    int n;
    while(scanf("%d",&n)){
        if(n == 0)
            break;
        for(int i=0;i<n;i++)
            scanf("%lf%lf",&N[i].x, &N[i].y);
        sort(N,N+n,cmpx);
        double ans = closestDis(0,n-1);
        printf("%.2lf\n",ans/2);
    }
    return 0;
}

解釋一下代碼中一些需要注意的地方,

sort(),cmpx(), cmpy()等就不比多說了

A[maxn]爲輔助數組,在合併時有用到,

重點說一下這一段代碼的break:

for(int i=0;i<k;i++){
        for(int j=i+1;j<k;j++){
            if( ( N[A[j]].y - N[A[i] ].y ) >=ans )
                break;
            ans = min(ans,dis(N[A[i]], N[A[j]]));
        }
    }
這個break是很有用的,在合併時我們在區間(mid-ans,min+ans)枚舉任意兩個點,但是這是對於x座標而言的,這個區間對應的點也有可能很多,所以我們要判斷一下y座標,如果兩個點的縱座標差大於ans了,那麼他們間的距離就不可能比當前最小距離還小了,而在這個循序前,我們已經對y進行了升序排序,所以直接break掉,減少不必要的運算。






發佈了30 篇原創文章 · 獲贊 13 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章