分治的运用---最近点对

问题很简单,就是在平面内有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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章