【转】求解平面最近点对的问题

美团面试,出了一道题,只想到暴力解法,分治硬是没想出来。。。。

 

FROM:http://www.cnblogs.com/xdruid/archive/2012/05/27/CP.html

 

(好吧。。。第一篇blog,之前没有写的习惯,现在还是开始慢慢记录下来吧~)

   所谓最近点对呢,就是平面上给你一堆点,然后求出这堆点中相距最小的距离。

   假如数据量比较小的话,那肯定是枚举所有点对求出各自距离再比较更方便了。很明显,枚举的时间复杂度是O(n²)。

不过一般不会给你这样的问题,点的个数往往很多,那就必然不能用枚举了。

   这里要使用一种O(nlog(n))的算法。

   这个算法主要思想就是分治,

   

算法描述:已知一个点的集合S,将S拆分成左右两部分求最近点对。首先对点集S进行排序,一般对点的X坐标从小到大排。算法每次选取一条直线(垂线)L,将S拆分成左右两部分Sl,Sr。L一般取点集S中所有点的中间点的x坐标来划分,这样可以保证SL和SR中的点数目各为n/2,否则,选取其他直线L划分可能导致Sl中有1个点,Sr中有n-1个点,这样算法的效率就很难保证了。依次找到Sl,Sr中的最短距离dl,dr。记录最短距离d=min(dl,dr);

如图:                                    

                                

很明显,只计算出Sl,Sr中的最短距离是不够的,S的最近点对很可能分别存在于Sl,Sr中。这种情况就要特殊处理了。

当然,只有在上图中两虚线之间的点才有再计算的意义,因为在这两条线之外的点与另一方的点的距离必然大于d。

 

    对于Sl虚框范围内的某点P,在Sr虚框中与P点距离小于d的点顶多只有六个,就是图二右图中的2个正方形的6的顶点。

    反证:假设Sr中存在一点Q与P的距离小于d,那么它与这个正方形的顶点的距离必然小于d,这与之前Sl,Sr中最短距离为d矛盾。

因此,对于Sl中的点P,不需要求出它与Sr中所有点的距离进行比较,根据鸽巢原理,只需要比较与P点Y坐标距离最近的6个点即可。这样大大降低了比较的次数。

求出Sl,Sr交界处的最短距离后对d进行更新即可。

                                                                          

 

算法时间复杂度: 首先对点集S的点x坐标和y坐标进行升序排序(快排),需要2nlogn次,

复杂度为O(2nlogn)在分治过程中,对于每个S'l中的点,都需要与S'r中的6个点进行比较,

复杂度为 O(n)。所以总的时间复杂度为O(nlogn),比起暴力枚举要好很多。           

               

HDU上的1007是一个裸的最近点对问题  HDU 1007
复制代码
//HDU 1007,代码模仿某位大神写的...Orz
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; #define MAXX 1<<30 struct point { double x,y; }; point p[100010]; int t[1000010]; double min(double a,double b) { return a>b?b:a; } int cmp(const point &a,const point &b) //对于点的x坐标排序; { if(a.x==b.x) return a.y<b.y; return a.x>b.x; } int cmpy(const int &a,const int &b) //对点的y坐标排序; { return p[a].y<p[b].y; } double dis(point a,point b) { return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } double findClosepari(int left,int right) //分治,递归。两部分中间的情况要特殊处理; { double x=MAXX; if(left==right) return x; if(left==right-1) return dis(p[left],p[right]); int mid=(left+right)>>1; //求出中间点; double dl=findClosepari(left,mid); double dr=findClosepari(mid+1,right); dl=min(dl,dr); int i,j,k; //下面把中间2dl宽度的部分的点单独处理; k=0; for(i=left;i<=right;i++) if(fabs(p[i].x-p[i+1].x)<dl) t[k++]=i; sort(t,t+k,cmpy); //由下到上排序,扫描; for(i=0; i<k; i++) for(j=i+1; j<k&&p[t[j]].y-p[t[i]].y<dl; j++) { double d3 = dis(p[t[i]],p[t[j]]); dl=min(dl,d3); } return dl; } int main() { int n; while(cin>>n,n) { for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y); sort(p+1,p+n-1,cmp); double r=findClosepari(1,n)/2.0; printf("%.2lf\n",r); } return 0; }
复制代码

posted on 2012-09-26 16:12  风程  阅读(1971)  评论(0编辑  收藏  举报

导航