【问题描述】
给定二维平面上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小。使用递归与分治策略求解二维平面上的最接近点对问题。假设所有点的集合为S,m为S中所有点的x坐标的中位数,垂直线x=m将集合S均匀分割为左右两个子集合S1和S2。
[输入]:
在屏幕上输入点的个数,以及所有点的x和y坐标。
[输出]:
第一次分割时,将所有点集合S分割为左右两个子集合S1和S2,分别输出左右子集合S1和S2,以及所有点集合S的最接近点对的距离以及最接近点对。
[样例输入]:
10
-15.4 -57.3
13.2 30.1
-87.5 93.2
47.6 -12.7
94.7 61.5
56.8 -57.1
27.8 43.5
-28.1 19.0
-96.2 47.5
55.5 -93.3
[样例输出]:
42.8
-28.1 19.0
13.2 30.1
36.2
55.5 -93.3
56.8 -57.1
19.8
13.2 30.1
27.8 43.5
[样例说明]:
输入:10个点,后续每行为每一点的x和y坐标。
输出:左右子集合S1和S2,以及所有点集合S的最接近点对的距离以及最接近点对。例如,前面三行中,S1的最接近点对的距离为42.8,最接近点对的x和y坐标分别为(-28.1,19.0)和(13.2,30.1)。输出最接近点对坐标时,先输出的点的x坐标小于后输出点的x坐标。中间三行和最后三行分别为子集合S2和集合S的最接近点对的距离以及最接近点对。
[问题分析]:
平面最近点对问题属于递归与分治类问题,此问题可以从一维最近点对问题开始研究:
当我们处理一个一维最近点对问题时,通过递归与分治的思想,我们可以不断对其进行二分,通过先对一维线上的点进行排序(此步骤是为了找中位数点的时候方便),然后找到这些点中的中位数mid,然后依此中位数为划分标准,对所有的点集合进行二分,划分为左集合和右集合,以此递归,在每层递归中,分别找到划分的左右集合中的最近点对,并选出最近的那一点对,然后再检查是否存在两个点一个在左集合,一个在右集合而这两点间的距离小于刚才找到的最近点对的距离(当然前人已经证明:如果最近点对出现在这种情况,在被划分的左右集合中最多各有一个点)。
然后再将此情景推广到二维平面点集合,划分标准中位数mid则由点集合中每个点的x坐标来求,mid也可以被看作是一条垂直的线x=mid,将这个二维平面进行了划分。接下来的步骤就跟一维最近点对问题的处理方法大致相同,不同的是,在出现最近点对的两个点是一个位于左集合,一个位于右集合时,不再是在被划分的左右集合中最多各有一个点,而是可能在左边出现n/2个点,右边最多出现6个点(这个结论前人也已经进行了证明)。
[算法步骤]:
1.将所有点按照x的进行排序
2.用x=mid将整个平面划分为两部分
3.求出x<=mid部分的最近点对距离d_left,求出x>mid部分的最近点对距离d_right,求出这两者中最小的距离记为d
4.找出所有x坐标在(mid-d,mid+d)范围中的点,放入res
5.对于(x,y),x∈(mid-d,mid),如果点(x1,y1)中x1>mid且|y-y1|<d,那么记录两点间距离,如果小于d,那么更新d,否则检查下一个点,以此类推,直至遍历完res中所有的点。
[伪代码]:
if(r-l==1) return dis(node[l],node[r])
if(r-l==2) return min(dis(node[l],node[r]),min(dis(node[l],node[l+1]),dis(node[l+1],node[r])))
else
sort(node,node+n,by_X)
mid=(l+r)/2
d=min(closet(node,l,mid),closet(node,mid+1,r))
for i=l to r
if(fabs(node[i].x-node[mid].x)<d) res.push_back(node[i])
sort(res.begin(),res.end(),by_Y)
for i=0 to res.size
for j=i+1 to res.size
if(fabs(res[i].y-res[j].y)<d)
min_dis=dis(res[i],res[j])
if(min_dis<d) d=min_dis
[时间复杂度分析]:
T(n)=1 当k=2时
T(n)=2T(n/2)+n 当k>2时
所以T(n)=O(nlog2n)
[代码实现]:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<bits/stdc++.h>
using namespace std;
typedef struct Point
{
double x,y;
}Point;
Point node[1000];
int n;
double dis(Point a,Point b)
{
return sqrt(pow((a.x-b.x),2)+pow((a.y-b.y),2));
}
bool by_X(Point a,Point b)//按x大小排序
{
if(a.x==b.x) return a.y<b.y;
else return a.x<b.x;
}
bool by_Y(Point a,Point b)//按y大小排序
{
if(a.y==b.y) return a.x<b.x;
else return a.y<b.y;
}
void swap(Point &a,Point &b)
{
Point c=a;
a=b;
b=c;
}
double closet(int l,int r,Point *best_node)
{
if(r-l==1)//两个点的情况
{
best_node[0]=node[l];
best_node[1]=node[r];
return dis(node[l],node[r]);
}
else if(r-l==2)//三个点的情况
{
double dis1=dis(node[l],node[l+1]);
double dis2=dis(node[l+1],node[r]);
double dis3=dis(node[l],node[r]);
if(dis1<dis2&&dis1<dis3)
{
best_node[0]=node[l];
best_node[1]=node[l+1];
return dis1;
}
if(dis2<dis1&&dis2<dis3)
{
best_node[0]=node[l+1];
best_node[1]=node[r];
return dis2;
}
if(dis3<dis2&&dis3<dis1)
{
best_node[0]=node[l];
best_node[1]=node[r];
return dis3;
}
}
else//大于三个点的情况
{
int mid=(l+r)/2;
Point temp1[2],temp2[2];
double d_left=closet(l,mid,best_node);
temp1[0]=best_node[0];
temp1[1]=best_node[1];
if(mid==(0+n-1)/2) //为了保证是在最外一层递归(即第一次分割的时候)的时候再输出
{
printf("%.1lf\n",d_left);
if(temp1[0].x>temp1[1].x) swap(temp1[0],temp1[1]);
printf("%.1lf %.1lf\n",temp1[0].x,temp1[0].y);
printf("%.1lf %.1lf\n",temp1[1].x,temp1[1].y);
}
double d_right=closet(mid+1,r,best_node);
temp2[0]=best_node[0];
temp2[1]=best_node[1];
if(mid==(0+n-1)/2)
{
printf("%.1lf\n",d_right);
if(temp2[0].x>temp2[1].x) swap(temp2[0],temp2[1]);
printf("%.1lf %.1lf\n",temp2[0].x,temp2[0].y);
printf("%.1lf %.1lf\n",temp2[1].x,temp2[1].y);
}
double d;
if(d_left<=d_right)
{
d=d_left;
best_node[0]=temp1[0];
best_node[1]=temp1[1];
}
if(d_left>d_right)
{
d=d_right;
best_node[0]=temp2[0];
best_node[1]=temp2[1];
}
vector<Point> res;
for(int i=l;i<=r;i++)//将距离mid距离小于d的点放入res
{
if(fabs(node[i].x-node[mid].x)<d)
{
res.push_back(node[i]);
}
}
sort(res.begin(),res.end(),by_Y);//将res中的点按照y的大小进行排序,便于后面的筛选
for(int i=0;i<res.size();i++)//通过i,j遍历res中所有的点对,从而找到距离最近的点对
{
for(int j=i+1;j<res.size();j++)
{
if(fabs(res[i].y-res[j].y)>d) break;
double min_dis=dis(res[i],res[j]);
if(min_dis<d)
{
d=min_dis;
best_node[0]=res[i];
best_node[1]=res[j];
}
}
}
return d;
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
double d;
for(int i=0;i<n;i++)
{
cin>>node[i].x;
cin>>node[i].y;
}
Point best_node[2];//记录最近点对
sort(node,node+n,by_X);
d=closet(0,n-1,best_node);
printf("%.1lf\n",d);
if(best_node[0].x>best_node[1].x) swap(best_node[0],best_node[1]);
printf("%.1lf %.1lf\n",best_node[0].x,best_node[0].y);
printf("%.1lf %.1lf",best_node[1].x,best_node[1].y);
}