该问题的形式化表示:
Input:平面上有n个点
Output:欧式距离最近的点对
1 暴力破解法,列举出这个平面所有的点对,找出最小的那个点对,所需要的时间复杂度为O(n^2)
2 用分而治之的思想
(1) 将平面上的点均分成左右两部分
分别求出左,右两边最近点对的距离d1,d2,δ =min(d1,d2)
然后我们需要考虑的是,是否存在来自分割线左右两侧的点其距离比 δ 还小,所以也需要计算分割线左右两侧的点组成的点对,如果将左右两侧的所有点对列举出来,时间开销为O(n^2)
但是,在算法中由于我们的目标是找到比 δ 还小的点对,所以我们只需要计算L区域内的点,L区域的宽度为 2δ
进一步分析
是否L区域内的所有点我们都需要进行计算,我们将L条带进行分割,如下图,格子的边长为 δ/2 ,显然一个格子中最多有一个点,因为如有两个点落在同一个格子中,则这两点的距离小于 δ 。
同样的我们在y轴方向进行分析,对于点 i 也只需要在2rows的区域内进行计算。因此对于任意一个L区域内分割线左边的点,其实最多只需要计算右边6个点即可。
代码如下
#include <iostream>
#include <math.h>
#include <algorithm>
#include <vector>;
#include <limits.h>
using namespace std;
#define MIN(a,b) ((a)<(b)? (a):(b))
struct point {
double x , y ;
}v[10000] ;
//存储L区域内的点
int v_strip[10000];
//计算两点之间的欧式距离
inline double dis( point a, point b ) {
return sqrt ( (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) ) ;
}
int comx(const point &a,const point &b){
return a.x < b.x ;
}
int comy(const int &a,const int &b){
return v[a].y < v[b].y ;
}
//分治法求最近点对
//先将所有的点在x轴方向上排序
double ClosestPairPre(int n) {
sort(v, v+n, comx);
return ClosestPair(0, n-1) ;
}
double ClosestPair (int low, int high) {
if ( (high-low)==0 ) {
return INT_MAX;
}
if ( (high-low)==1 ) {
return dis(v[low],v[high]) ;
}
//divide and conquer
int mid = (low+high)>>1 ;
double dis_left = ClosestPair(low, mid) ;
double dis_right = ClosestPair(mid+1, high) ;
double dis_min = MIN(dis_left,dis_right) ;
//record points in the 2*dis_min strip by x-coordinate
int strip_low=0, strip_num=0 ;
for (int i = low; i <= high; ++i) {
if ( abs(v[i].x-v[mid].x) <= dis_min ){
v_strip[strip_num++] = i;
}
}
sort(v_strip, v_strip+strip_num, comy);
//在strip中对任何一点,只需要和另一边的6个比较
for (int i = strip_low; i < strip_num; ++i){
int k=(i+7 > strip_num) ? strip_num:(i+7) ;
for (int j = i+1; j < k; ++j){
if ( ( v[v_strip[j]].y-v[v_strip[i]].y)>dis_min )
break;
dis_min = MIN(dis_min, dis(v[v_strip[i]], v[v_strip[j]])) ;
}
}
return dis_min;
}