原题链接:点击打开链接
参考了网上的各种资料,算是摸清了这题的思路。
在坐标系中有许多个点,给出点的坐标,要求的是最近两点的距离。
很明显,暴力虽然能得到结果,但是必然超时。因此,使用分治思想。
简单的说,首先,将输入的点按照x轴的大小顺升序。将当前范围内的点均分为左右两个部分。此时,以两部分中间的点为界,当前区域内距离最小的点可能出现两种情况:1是两点被分于同一侧,2是两者分居中线两侧。而如果我们将这种一分为二的步骤继续下去(很显然,这一现象对任一层递归都适用),直到递归的分支中仅仅存在两个或者三个点时向上回溯。
便可以看出:如果最近的两点出现在当前区域的同侧,则两点坐标必然相邻。通过层层将最近点的距离回溯,我们可以找到这种情况的最小点。
然而,回溯时会遇上问题:如果最近点出现在中线两侧,则不能被发现。
为了解决这个问题,我们在每一层回溯的结束之后,对各个点的Y坐标进行一次升序,然后比较各个符合条件的点的距离(此时得出异侧最小值),接着与之前得出的同侧最小值比较,最终得到整个问题的最短距离。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=5+1e5;
class node //创建“点”类型
{
public:
double x,y;
}p1[maxn],p2[maxn];
bool cmpx(node a,node b) //快排的比较函数,用来对类内部的x升序
{
return a.x<b.x;
}
bool cmpy(node a,node b) //对类内部的y升序
{
return a.y<b.y;
}
double dis(node a,node b) //两点距离
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double min(double a,double b)
{
return a>b?b:a;
}
double mindis(int a,int b) //求最小距离
{
if(b-a==1) //当只有两个点时
return dis(p1[a],p1[b]);
if(b-a==2) //当有3个点时
return min(min(dis(p1[a], p1[a+1]), dis(p1[a+1], p1[a+2])), dis(p1[a], p1[a+2]));
int m=(a+b)>>1; //m为中点
double mini;
mini=min(mindis(a,m),mindis(m+1,b)); //递归调用,得出每层位于同侧的最近点距离;
int count=0; //找出可能存在有最近点位于中点两边的点,存于p2对象组;
for(int i=a;i<=b;i++)
{
if(fabs(p1[i].x-p1[m].x)<=mini)
{
p2[count]=p1[i];
count++;
}
}
sort(p2,p2+count,cmpy); //按y坐标再次升序;
for(int i=0;i<count;i++) //查找可能存在的最近点;
for(int j=i+1;j<count;j++)
{
if(p2[j].y-p2[i].y>=mini)
break;
else if(dis(p2[i],p2[j])<mini)
mini=dis(p2[i],p2[j]);
}
return mini;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF&&n)
{
for(int i=0;i<n;i++)
scanf("%lf %lf",&p1[i].x,&p1[i].y);
sort(p1,p1+n,cmpx);
printf("%.2lf\n",mindis(0,n-1)/2);
}
}