首先说一下这题的题意,题意很简单,就是给出平面上N个点的横坐标与纵坐标,求最近的两点的距离(2<=N<=1000000)。
这题首先很容易想到朴素的方法:分别枚举两个点,通过比较找出其中的最小值,这是O(N^2)的算法,对于N<=1000000的数据范围,是肯定会超时的,这就要我们想其他方法。
那么现在介绍的就是一个高效求最近点对的方法。一看这数据范围,就会想到这应该是一个O(nlogn)的算法。那么首先想到的应该就是分治的思想,我们对于x轴,将平面上的点分成两部分,如下图所示:
我们可以很容易想到,最近点对的分布有三种可能性,第一种就是两个点均在mid的左边,第二种就是两个点均在mid的右边,第三种情况就是这两个点分别分布在mid的左边和右边。第一种情况和第二种情况的最近点,可以通过比较相邻的点来获得,我们记得到的最近点的距离为d。接下来最困难的部分就是合并了,我们知道,两个点坐标的距离公式是(x1-x2)^2-(y1-y2)^2,也就是说,这两个点横坐标的距离和纵坐标的距离都要小于d,这样这两个点才有可能是最近点对,否则不可能为最近点对,这样我们就进行了一个类似于剪枝的操作,算法的复杂度也就在这个处理上由O(n^2)降到了O(n*logn*logn),接下来只需要通过递归来一层层推回去即可。
接下来附上代码:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#define maxnum 2147483647
using namespace std;
int n;
typedef struct
{
double x,y;
} atp;
atp a[100005];
double mini(double x,double y)
{
return x<y?x:y;
}
bool cmp1(atp a,atp b)
{
return a.x<b.x;
}
bool cmp2(atp a,atp b)
{
return a.y<b.y;
}
double work(int f,int r)
{
int mid,i,j,ff,rr;
double ans; //注意类型1.不要使用int类型 2.float精度不够,话说float精度有这么低么。。。郁闷
if(f==r) return maxnum;
else
{
if((r-f)==1) return ((a[r].x-a[f].x)*(a[r].x-a[f].x)+(a[r].y-a[f].y)*(a[r].y-a[f].y)); //递归的结束条件是要最先考虑的
else
{
mid=(f+r)/2;
ans=mini(work(f,mid),work(mid+1,r));
//找到y符合的区间
for(i=mid;i>=f;i--)
if(a[mid].y-a[i].y>ans)
break;
for(j=mid+1;j<=r;j++)
if(a[j].y-a[mid].y>ans)
break;
ff=i+1;rr=j-1;
sort(a+ff,a+rr+1,cmp1); //对y符合的区间以x为关键字进行排序
//筛选符合要求的x区间,并对其进行最终确认
for(i=ff;i<rr;i++)
{
for(j=i+1;j<=rr && a[j].x-a[i].x<ans;j++)
{
ans=mini(ans,(a[i].x-a[j].x)*(a[i].x-a[j].x)+(a[i].y-a[j].y)*(a[i].y-a[j].y));
}
}
return ans;
}
}
}
int main()
{
int i;
double ans;
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
for(i=1;i<=n;i++)
scanf("%lf%lf",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp2); //c++ sort 的规则要注意
ans=work(1,n);
printf("%.2lf\n",sqrt(ans)/2);
}
return 0;
}