最近點對問題(分治)
阿新 • • 發佈:2019-01-06
首先感謝伯樂的點評,已修改~~
問題描述:
給定n個點的橫縱座標,求它們的最近距離。
例
2
1 0
0 1
輸出1.4142
問題分析:
若是直接暴力求取,複雜度為O(n^2),從n個點選出2個點,n*(n-1)/2嘛
而如果用分治法,複雜度可以降到O(nlogn),方法是先根據橫縱座標從小到大排序
然後尋找left~mid(點全部在左邊),mid+1~right(點全部在右邊)的最小點對,再將它們兩個中的最小值和一個點在左邊,一個點在右邊的情況比較(有點像分治法求最大子序列和),先不管中間的處理,遞迴到什麼情況為止呢,如果left==right則說明只有一個點,返回無窮大,如果left+1==right說明有兩個點,直接返回距離
再回過頭來考慮中間的情況(一個點在左邊,一個點在右邊),考慮到之前已經找到最短距離為d,所以中間的點對已經是在距離2d*2d的正方形內(這點很重要!)
嚴格來說應該是圓內不過圓計算太麻煩且有精度問題。
找出這些點,將他們一一組合可以得到他們的最短距離,如果比d小就更新d.
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> using namespace std; int n; struct node { double x; double y; }; int cmp(node a,node b)//兩個點按照橫座標從小到大排序,相同則按照縱座標從小到大 { if (a.x==b.x) return a.y<b.y; return a.x<b.x; } node point[10005]; int cmp2(int a,int b) { return point[a].y<point[b].y; } double distance(double c1,double c2,double d1,double d2) { return sqrt((c1-d1)*(c1-d1)+(c2-d2)*(c2-d2)); } double calculate(int left,int right)//left是最左邊的座標,right是最右邊的座標 { int i,j,mid,tempnumber; double d,d1,d2; int temp[10005]; if (left==right)//如果是一個點,那麼距離自然是0 return 999999;//返回一個無窮大 if (left+1==right)//如果是兩個點,直接返回兩點的距離 return distance(point[left].x,point[left].y,point[right].x,point[right].y); mid=(left+right)>>1; d1=calculate(left,mid);//左部分的最近點對的距離 d2=calculate(mid+1,right);//右部分的最近點對的距離 d=d1<d2?d1:d2;//取較小的,接下來要考慮一個點在左部分,一個點在右部分的情況 i=mid; tempnumber=0; while(i>=0&&point[mid].x-point[i].x<d)//從中點往左邊找 { temp[tempnumber++]=i; i--; } i=mid+1; while(i<n&&point[i].x-point[mid].x<d)//從中點往右邊找(注意這次不含mid是從mid+1,避免重複mid) { temp[tempnumber++]=i; i++; } sort(temp,temp+tempnumber,cmp2);//將得到的temp按照縱座標從小到大排序,這樣一旦某一個j的縱座標大於i,那麼接下來的j也不用考慮(其實個人感覺效率差距不大,畢竟你還要排序,不過網上都這樣就隨波逐流了...) for (i=0;i<tempnumber;i++) for (j=i+1;j<tempnumber;j++) { if (point[temp[j]].y-point[temp[i]].y>d) break;//如果已經大於d直接跳出迴圈 else { d1=distance(point[temp[i]].x,point[temp[i]].y,point[temp[j]].x,point[temp[j]].y);//否則看範圍內是否還有小於d的點對 d=d<d1?d:d1; //更新 } } return d; } int main() { int i,j; scanf("%d",&n); for (i=0;i<n;i++) scanf("%lf%lf",&point[i].x,&point[i].y); sort(point,point+n,cmp); printf("%.4f\n",calculate(0,n-1)); return 0; }