最近點對演算法分析Closest Pair of Points
阿新 • • 發佈:2019-02-18
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;
}