1. 题意
在 n>= 2的点的集合中寻找最近点对,最近点对是指通常意义下的欧几里得距离:即点p1(x1, y1), p2(x2, y2)之间的距离为sqrt((x1 - x2)^2 + (y1- y2)^2),当Q中有2点重合时,最近点对的距离是0;
最简单的算法是暴力搜索,查看所有的O(n^2)个点对。现在利用分治法解决,其运行时间是T(n) = 2 T(n/2) + O(n),该算法的时间复杂度为O(nlgn);
2. 分治法
对于每次递归调用的输入为子集P,P中的所有点按其X坐标的单调递增的顺序排序,
1) 当输入子集P的递归调用首先检查是否P中点个数是否小于3,如果小于3,则按暴力搜索的方案解决;
2) 当输入子集p中的点的个数大于3时,则递归调用下面的分支方法:
分解过程:
找出一条垂线l, 他把输入点集P划分为满足一下条件的两个集合PL和PR:|PL|=[|P| / 2], |PR|=[|P| / 2], PL中的所有点在线l上或者l的左侧,PR中所有点在线l上或者l的右侧;子集P中所有点按X坐标单调递增的顺序进行排序。
解决过程:
把P划分为PL和PR后,在进行2次递归调用,一次找出PL中的最近点对,另一次找出PR中的最近点对,第一次调用的输入为子集PL,第二次调用的输入为子集PR。对对PL和PR,返回最近点对的距离分别是minL和minR,则置minD =min(minL, minR);
合并过程:
最近点对要么是某次递归调用找出的距离为minD的点对,要么是PL中的一个点和PR中的一个点组成的点对,所以我们要确定的是是否存在其距离小于minD的一个点对。若存在这样的点对,则点对中的两个点必定在距离垂直线l的minD距离范围内。因此他们都处在以垂线l为中心宽度为2*minD的垂直带型区域中,如图1所示。
为了找出这样的点对算法需要做这样的工作:
1) 把数组中PL或者PR中所有水平距离不在宽度为2* minD的带型区域中的点去掉后得到点集P1;
2) 再把点集p1中所有点对的垂直距离不在minD之内的点去掉后得到点集P2;
3) 在点集P2中计算每个点对得到欧几里的距离minX,然后minX跟minD比较取较小值,返回较小值
图 1
证明最近点对需要检查子集P中至多有8个点可能位于minD* 2minD的一些关键概念:
1) 如果pl属于PL,pr属于PR,并且pl和pr之间距离小于minD,那么他们必定位于以直线l为中心线的minD * 2minD的矩形区域内。
2) 4个两两之间的距离至少为minD的必定位于同一个minD * minD正方形内。
图2
考察图2矩形左半边的minD *mind的正方形,因为PL所有点之间的距离至少为minD距离,所以至多有4个点位于该正方形内即正方形的四个顶点,显然正方形内部的点不能满足PL中所有点距离至少为minD;类似的PR中至多有4个点可能位于该矩形右半边的minD* minD正方形内。直线l上至多含有4个点,若有两队重合点,没对包含一个PL中点和一个PR中的点,一对就是直线l和矩形上面一条边的交汇处,另一对就是直线l和矩形下面一条边的交汇处。因此点集P中至多含有8个点肯能位于矩形内。
示例代码:
#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
#define NSIZ 1100000
typedef struct Node_
{
int x, y;
}Node;
Node nodes[NSIZ];
bool cmp(Node a, Node b)
{
return a.x < b.x;
}
double Distance(Node a, Node b)
{
double dis = (a.x - b.x) * ( a.x - b.x) + (a.y - b.y)* (a.y - b.y) * 1.0;
return sqrt(dis);
}
double mergePoint(intleft,int right)
{
double resultLeft = 0, resultRight = 0, minLen =INT_MAX;
int i = 0, j = 0;
double tmp = 0;
if (right > left + 4)
{
int mid = left + ((right - left) >> 1);
resultLeft= mergePoint(left, mid);
resultRight= mergePoint(mid, right);
minLen= min(resultLeft, resultRight);
for (i = mid; i >= left && nodes[i].x >nodes[mid].x - minLen; --i)
{
for (j = mid; j <= right && nodes[j].x< nodes[mid].x + minLen; ++j )
{
if (abs(nodes[j].y - nodes[j].y) < minLen)
{
tmp= Distance(nodes[i], nodes[j]);
if (tmp < minLen)
{
minLen= tmp;
}
}
}
}
}
else
{
for (i = left; i < right; ++i)
{
for (j = left + 1; j <= right; ++j)
{
if (i != j )
{
tmp= Distance(nodes[i], nodes[j]);
if (tmp < minLen)
{
minLen= tmp;
}
}
}
}
}
return minLen;
}
int main()
{
int t, n, i, mid;
double res;
scanf("%d", &t);
while(t--)
{
res= 0;
scanf("%d", &n);
for (i = 0;i < n; ++i)
{
scanf("%d %d", &nodes[i].x,&nodes[i].y);
}
sort(nodes,nodes + n, cmp);
res= mergePoint(0, n - 1);
printf("%.3lf\n", res);
}
return 0;
}