1. 程式人生 > >最近點對問題(分治)

最近點對問題(分治)

首先感謝伯樂的點評,已修改~~

問題描述:

給定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;
}