平面最近点对(学习笔记)

先来推荐几篇.其实大家只要认真看了这些博客,就没必要看我的了QWQ.

平面最近点对问题:在同一平面内的所有点中,找出距离最近的两个点.

既然有模板题,还是双倍经验,那就直接来讲吧.首先暴力应该都想得到,就是\(n^2\)枚举所有点,算出所有点两两之间的距离,同时比较大小,记录答案.

考虑如何优化?核心思想是分治.至于为什么要用分治,介绍算法框架的时候自然而然就明白了.

设solve(V)为点集V中的最近点对的距离,那么,要求点集V的最近点对,我们先对其按x坐标从小到大排序,然后考虑选择一条垂直分割线,把V划分为点数尽可能相等的两部分V1和V2(说直白了就是选择点集V最中间两个点的中线),则solve(V)=min{solve(V1),solve(V2), dis(u,v)⁡|⁡u∈V1,v∈V2}.

如何理解这个式子?关于点集V中的最近点对,无非只有三种情况,两个点都在V1中,两个点都在V2中,或者一个点在V1中,另一个点在V2中.前两种情况就是分治的子问题,我们递归下去求解就好了.那么第三种情况如何高效求解呢?

令d=min{solve(V1),solve(V2)},那么我们可以只需要考虑已经选择好的垂直分割线往两端各延伸d的这一个区域,因为只有这个区域内的点对才可能有比d更小的距离(这里不懂的话,画个图很快就明白了)


solve(V){
	选择一条垂直分割线,它将V分割为V1,V2;
	d=min(solve(V1),solve(V2));
	取出垂直分割线往两端延伸d的区域内的所有点;
	求出区域内最近距离d’;
	return min(d,d’);
}

到了这里,问题只剩最后一步,如何求出区域内最近距离d’,上面那一段文字只是优化了求解的时间复杂度,仍未提到具体地如何做?对于区域内的点,我们对其按y坐标排序;对于每个点,找到y坐标与它相差不超过d的点,计算距离d’,更新答案.

对y排序时,sort排序,总的时间复杂度\(nlog_nlog_n\):

int n,b[200005];
struct point{
    double x,y;
}a[200005];
bool cmpx(point x,point y){return x.x<y.x;}
bool cmpy(int x,int y){return a[x].y<a[y].y;}
double dis(point x,point y){
	return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
 }
//l,r是点的下标,表示本次处理由第l~r个点构成的点集
double solve(int l,int r){
    if(l==r)return 1e18;
    if(l+1==r)return dis(a[l],a[r]);
//亲测:这一句可以不写,上面那个边界条件必须要写
    int mid=(l+r)>>1;
//找到分割点,即点集中最中间的点
    double d=min(solve(l,mid),solve(mid+1,r));
//分治
    int k=0;
    for(int i=l;i<=r;i++)
    	if(fabs(a[i].x-a[mid].x)<=d)b[++k]=i;
    sort(b+1,b+k+1,cmpy);
    for(int i=1;i<=k;i++)
    for(int j=i+1;j<=k;j++)
        if(a[b[j]].y-a[b[i]].y<=d)
        	d=min(d,dis(a[b[i]],a[b[j]]));
    return d;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++)
    	scanf("%lf%lf",&a[i].x,&a[i].y);
    sort(a+1,a+n+1,cmpx);
    printf("%.4lf\n",solve(1,n));
    return 0;
}

优化:对y排序时,归并排序,总的时间复杂度\(nlog_n\)(亲测:快了100+ms)

int n;
struct point{
    double x,y;
}a[200005],b[200005];
bool cmpx(point x,point y){return x.x<y.x;}
bool cmpy(point x,point y){return x.y<y.y;}
double dis(point x,point y){
	return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
}
double solve(int l,int r){
    if(l==r)return 1e18;
    int mid=(l+r)>>1;
    double midline=(a[mid].x+a[mid+1].x)/2;
//比较两份代码,就会发现,上面的分割线直接就是a[mid].x
//而这里的分割线是(a[mid].x+a[mid+1].x)/2
//显然此处更为严谨,这里以a[mid].x作为分割线会WA
    double d=min(solve(l,mid),solve(mid+1,r));
    int i=l,j=mid+1,k=i;
    while(i<=mid&&j<=r){
		if(cmpy(a[i],a[j]))b[k]=a[i],i++,k++;
		else b[k]=a[j],j++,k++;
    }
    if(i<=mid)while(i<=mid)b[k]=a[i],i++,k++;
    else while(j<=r)b[k]=a[j],j++,k++;
    for(int i=l;i<=r;i++)a[i]=b[i];
    int L=1,R=0;
    for(int i=l;i<=r;i++)
	if(fabs(a[i].x-midline)<=d){
	    while(L<=R&&(a[i].y-b[L].y)>=d)L++;
//优化时间复杂度,把一定不符合要求的点先去掉再进循环.
//这里没有的话就会TLE
	    for(int j=L;j<=R;j++)
			d=min(d,dis(a[i],b[j]));
	    b[++R]=a[i];
	}
    return d;
}

sort排序的写法比归并排序更加直接简单,后者细节较多,但时间效率更高,一般而言,sort排序就足够了.

posted on 2019-02-11 21:53  PPXppx  阅读(220)  评论(0编辑  收藏  举报