問題很簡單,就是在平面內有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) 了,
於是就有了下面的思路:
分別遞歸求出左半邊和右半邊的最短距離,然後合併
求解時我們分兩種情況
(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掉,減少不必要的運算。