设平面上的点按x排序好了,这样最多增加O(N*logN),这再整个算法来看并没有增加复杂度级别。
排好序后,可以划一条垂线,把点集分成两半:PL和PR。于是最近点对或者在PL中,或者在PR中,或者PL,PR各有一点。
把三种距离情况定义为dL, dR, dC.
其中dL, dR可以递归求解,于是问题就变为计算dC。 根据上面红色字解释,由于我们希望得到O(N*logN)的解,因此必须能够仅仅多花O(N)的附加工作计算dC。
另s=min(dL, dR). 通过观察能得出结论:如果dC<s,即dC对s有所改进,则只需计算dC。如果dC满足这样的条件,则决定dC的两点必然在分割线的s距离之内,称之为带(strip)
否则不可能满足dC<s, 于是缩小了需要考虑的点的范围。
如果是均匀分布的点集,则能证明出在该带中平均只有O(sqrt(N))个点,(注:书上这么写的,我也不会证,先记下这个理论吧)。因此,对这些点运用蛮力法可以在O(N)时间内完成。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#define MM 100020
using namespace std;
struct Note
{
double x;
double y;
}pt[MM];
int _sort1[MM];
int _sort2[MM];
double min(double& x,double& y)
{
if(x-y>=1e-6)
return y;
else return x;
}
int cmp_x(const void *a,const void *b)
{
if(((Note*)a)->x>((Note*)b)->x) return 1;
else return -1;
}
int cmp_y(const void *a,const void *b)
{
if(((Note*)a)->y>((Note*)b)->y) return 1;
else return -1;
}
double distan(Note a,Note b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double neardis(int first,int ends)
{
if(ends-first==1) return distan(pt[first],pt[ends]);
if(ends-first==2) return min(min(distan(pt[first],pt[first+1]),distan(pt[first],pt[ends])),distan(pt[first+1],pt[ends]));
//2个点或3个点情况下返回
int mid=(first+ends)/2;
double dis1=neardis(first,mid);
double dis2=neardis(mid+1,ends);
//划分问题
//划分问题
double dota=min(dis1,dis2);
int _end1=0;
int _end2=0;
for(int i=mid;i>first&&distan(pt,pt[mid])<=dota;i--)
{
_sort1[_end1++]=i;
}
for(int i=mid+1;i<ends&&distan(pt,pt[mid])<=dota;i++)
{
_sort2[_end2++]=i;
}
//统计在分界线两边的点的情况;
double min_dis=dota;
for(int i=0;i<_end1;i++)
{
for(int j=0;j<_end2;j++)
{
dota=distan(pt[_sort1],pt[_sort2[j]]);
min_dis=min(min_dis,dota);
}
}
//计算在分界线两边的点的距离,与最小距离比较。
return min_dis;
}
int main()
{
int n;
cin>>n;
while(n)
{
for(int i=0;i<n;i++)
cin>>pt.x>>pt.y;
qsort(pt,n,sizeof(pt[0]),cmp_x);
cout<<fixed<<setiosflags(ios::showpoint)<<setprecision(2)<<neardis(0,n-1)<<endl;
cin>>n;
}
return 0;
}