问题场景:在应用中,常用诸如点、圆等简单的几何对象代表现实世界中的实体。在涉及这些几何对象的问题中,常需要了解其邻域中其他几何对象的信息。例如,在空中交通控制问题中,若将飞机作为空间中移动的一个点来看待,则具有最大碰撞危险的2架飞机,就是这个空间中最接近的一对点。这类问题是计算几何学中研究的基本问题之一。
问题描述:给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。
1、一维最接近点对问题
算法思路:
这个问题很容易理解,似乎也不难解决。我们只要将每一点与其他n-1个点的距离算出,找出达到最小距离的两个点即可。然而,这样做效率太低,需要O(n^2)的计算时间。在问题的计算复杂性中我们可以看到,该问题的计算时间下界为Ω(nlogn)。这个下界引导我们去找问题的一个θ(nlogn)算法。采用分治法思想,考虑将所给的n个点的集合S分成2个子集S1和S2,每个子集中约有n/2个点,然后在每个子集中递归地求其最接近的点对。在这里,一个关键的问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点对,因为S1和S2的最接近点对未必就是S的最接近点对。如果组成S的最接近点对的2个点都在S1中或都在S2中,则问题很容易解决。但是,如果这2个点分别在S1和S2中,则对于S1中任一点p,S2中最多只有n/2个点与它构成最接近点对的候选者,仍需做n^2/4次计算和比较才能确定S的最接近点对。因此,依此思路,合并步骤耗时为O(n^2)。整个算法所需计算时间T(n)应满足: T(n)=2T(n/2)+O(n^2)。它的解为T(n)=O(n^2),即与合并步骤的耗时同阶,这不比用穷举的方法好。从解递归方程的套用公式法,我们看到问题出在合并步骤耗时太多。这启发我们把注意力放在合并步骤上。
设S中的n个点为x轴上的n个实数x1,x2,..,xn。最接近点对即为这n个实数中相差最小的2个实数。我们显然可以先将x1,x2,..,xn排好序,然后,用一次线性扫描就可以找出最接近点对。这种方法主要计算时间花在排序上,在排序算法已经证明,时间复杂度为O(nlogn)。然而这种方法无法直接推广到二维的情形。因此,对这种一维的简单情形,我们还是尝试用分治法来求解,并希望能推广到二维的情形。假设我们用x轴上某个点m将S划分为2个子集S1和S2,使得S1={x∈S|x≤m};S2={x∈S|x>m}。这样一来,对于所有p∈S1和q∈S2有p1
和S2上找出其最接近点对{p1,p2}和{q1,q2},并设d=min{|p1-p2|,|q1-q2|},S中的最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{p3,q3},其中p3∈S1且q3∈S2。如图所示。
如果S的最接近点对是{p3,q3},即|p3-q3|
T(n)=T(n-1)+O(n)
它的解是T(n)=O(n^2)。这种效率降低的现象可以通过分治法中“平衡子问题”的方法加以解决。即通过适当选择分割点m,使S1和S2中有大致相等个数的点。自然地,我们会想到用S的n个点的坐标的中位数来作分割点。在选择算法中介绍的选取中位数的线性时间算法使我们可以在O(n)时间内确定一个平衡的分割点m。
本程序确定平衡点采用m=[max(S)+min(S)]/2方法。如果需要利用中位数作分割点,看结合笔者博文《0005算法笔记——线性时间选择》改写。
一维最接近临近点对问题程序清单如下:
//2d10-1 一维最邻近点对问题
#include "stdafx.h"
#include
#include
using namespace std;
const int L=100;
//点对结构体
struct Pair
{
float d;//点对距离
float d1,d2;//点对坐标
};
float Random();
int input(float s[]);//构造S
float Max(float s[],int p,int q);
float Min(float s[],int p,int q);
template
void Swap(Type &x,Type &y);
template
int Partition(Type s[],Type x,int l,int r);
Pair Cpair(float s[],int l,int r);
int main()
{
srand((unsigned)time(NULL));
int m;
float s[L];
Pair d;
m=input(s);
d=Cpair(s,0,m-1);
cout<>length;
cout<<"点集在X轴上坐标为:";
for(int i=0;is[i])
s_min=s[i];
return s_min;
}
template
void Swap(Type &x,Type &y)
{
Type temp = x;
x = y;
y = temp;
}
template
int Partition(Type s[],Type x,int l,int r)
{
int i = l - 1,j = r + 1;
while(true)
{
while(s[++i]x);
if(i>=j)
{
break;
}
Swap(s[i],s[j]);
}
return j;
}
//返回s[]中的具有最近距离的点对及其距离
Pair Cpair(float s[],int l,int r)
{
Pair min_d={99999,0,0};//最短距离
if(r-l<1) return min_d;
float m1=Max(s,l,r),m2=Min(s,l,r);
float m=(m1+m2)/2;//找出点集中的中位数
//将点集中的各元素按与m的大小关系分组
int j = Partition(s,m,l,r);
Pair d1=Cpair(s,l,j),d2=Cpair(s,j+1,r);//递归
float p=Max(s,l,j),q=Min(s,j+1,r);
//返回s[]中的具有最近距离的点对及其距离
if(d1.d
程序运行结果如下:
该算法的分割步骤和合并步骤总共耗时O(n)。因此,算法耗费的计算时间T(n)满足递归方程:
解此递归方程可得T(n)=O(nlogn)。
2、二维最接近点对问题
将以上过程推广到二维最接近点对问题,设S中的点为平面上的点,它们都有2个坐标值x和y。为了将平面上点集S线性分割为大小大致相等的2个子集S1和S2,我们选取一垂直线l:x=m来作为分割直线。其中m为S中各点x坐标的中位数。由此将S分割为S1={p∈S|px≤m}和S2={p∈S|px>m}。从而使S1和S2分别位于直线l的左侧和右侧,且S=S1∪S2 。由于m是S中各点x坐标值的中位数,因此S1和S2中的点数大致相等。递归地在S1和S2上解最接近点对问题,我们分别得到S1和S2中的最小距离d1和d2。现设d=min(d1,d2)。若S的最接近点对(p,q)之间的距离d(p,q)
距直线l的距离小于d的所有点
在一维的情形,距分割点距离为d的2个区间(m-d,m](m,m+d]中最多各有S中一个点。因而这2点成为唯一的末检查过的最接近点对候选者。二维的情形则要复杂些,此时,P1中所有点与P2中所有点构成的点对均为最接近点对的候选者。在最坏情况下有n2/4对这样的候选者。但是P1和P2中的点具有以下的稀疏性质,它使我们不必检查所有这n^2/4对候选者。考虑P1中任意一点p,它若与P2中的点q构成最接近点对的候选者,则必有d(p,q)
包含点q的dX2d矩形R
由d的意义可知P2中任何2个S中的点的距离都不小于d。由此可以推出矩形R中最多只有6个S中的点。事实上,我们可以将矩形R的长为2d的边3等分,将它的长为d的边2等分,由此导出6个(d/2)×(2d/3)的矩形。如左图所示:
矩阵R中点的稀疏性
若矩形R中有多于6个S中的点,则由鸽舍原理易知至少有一个δ×2δ的小矩形中有2个以上S中的点。设u,v是这样2个点,它们位于同一小矩形中,则:
因此d(u,v)≤5d/6
程序清单如下:
//2d10-2 二维最邻近点对问题
#include "stdafx.h"
#include
#include
#include
using namespace std;
const int M=50;
//用类PointX和PointY表示依x坐标和y坐标排好序的点
class PointX {
public:
int operator<=(PointX a)const
{ return (x<=a.x); }
int ID; //点编号
float x,y; //点坐标
};
class PointY {
public:
int operator<=(PointY a)const
{ return(y<=a.y); }
int p; //同一点在数组x中的坐标
float x,y; //点坐标
};
float Random();
template
float dis(const Type&u,const Type&v);
bool Cpair2(PointX X[], int n,PointX& a,PointX& b, float& d);
void closest(PointX X[],PointY Y[],PointY Z[], int l, int r,PointX& a,PointX& b,float& d);
template
void Copy(Type a[],Type b[], int left,int right);
template
void Merge(Type c[],Type d[],int l,int m,int r);
template
void MergeSort(Type a[],Type b[],int left,int right);
int main()
{
srand((unsigned)time(NULL));
int length;
cout<<"请输入点对数:";
cin>>length;
PointX X[M];
cout<<"随机生成的二维点对为:"<
inline float dis(const Type& u,const Type& v)
{
float dx=u.x-v.x;
float dy=u.y-v.y;
return sqrt(dx*dx+dy*dy);
}
bool Cpair2(PointX X[], int n,PointX& a,PointX& b,float& d)
{
if(n<2) return false;
PointX* tmpX = new PointX[n];
MergeSort(X,tmpX,0,n-1);
PointY* Y=new PointY[n];
for(int i=0;im) Z[g++]=Y[i];
else Z[f++]=Y[i];
}
closest(X,Z,Y,l,m,a,b,d);
float dr;
PointX ar,br;
closest(X,Z,Y,m+1,r,ar,br,dr);
if(dr
void Merge(Type c[],Type d[],int l,int m,int r)
{
int i = l,j = m + 1,k = l;
while((i<=m)&&(j<=r))
{
if(c[i]<=c[j])
{
d[k++] = c[i++];
}
else
{
d[k++] = c[j++];
}
}
if(i>m)
{
for(int q=j; q<=r; q++)
{
d[k++] = c[q];
}
}
else
{
for(int q=i; q<=m; q++)
{
d[k++] = c[q];
}
}
}
template
void MergeSort(Type a[],Type b[],int left,int right)
{
if(left
void Copy(Type a[],Type b[], int left,int right)
{
for(int i=left;i<=right;i++)
a[i]=b[i];
}
程序结果: