最近点对问题I.一维问题:一、问题描述和分析最近点对问题的提法是:给定平面上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小。
严格的讲,最接近点对可能多于1对,为简单起见,只找其中的1对作为问题的解。
简单的说,只要将每一点与其它n-1个点的距离算出,找出达到最小距离的2点即可。
但这样效率太低,故想到分治法来解决这个问题。
也就是说,将所给的平面上n个点的集合S 分成2个子集S1和S2,每个子集中约有n/2个点。
然后在每个子集中递归的求其最接近的点对。
这里,关键问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点对。
如果组成S的最接近点对的2个点都在S1中或都在S2中,则问题很容易解决,但如果这2个点分别在S1和S2中,问题就不那么简单了。
下面的基本算法中,将对其作具体分析。
二、基本算法假设用x轴上某个点m将S划分为2个集合S1和S2,使得S1={x∈S|x<=m};S2={x ∈S|x>m}。
因此,对于所有p∈S1和q∈S2有p<q。
递归的在S1和S2上找出其最接近点对{p1,p2}和{q1,q2},并设d=min{|p1-p2|,|q1-q2|}。
由此易知,S中的最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{p3,q3},其中p3∈S1且q3∈S2。
如下图所示:S1 S2p1 p2 p3 q1 q2 q3图1 一维情形的分治法注意到,如果S的最接近点对是{p3,q3},即|p3-q3|<d,则p3和q3两者与m的距离不超过d,即|p3-m|<d,|q3-m|<d。
也就是说,p3∈(m-d,m],q3∈(m,m+d]。
由于每个长度为d的半闭区间至多包含S1中的一个点,并且m是S1和S2的分割点,因此(m-d,m]中至少包含一个S中的点。
同理,(m,m+d]中也至少包含一个S中的点。
由上图知,若(m-d,m]中有S的点,则此点就是S1中最大点。
同理,若(m,m+d]中有S的点,则此点就是S2中最小点。
因此,用线性时间就可以找到区间(m-d,m]和(m,m+d]中所有点,即p3和q3。
从而用线性时间就可以将S1的解和S2的解合并成为S的解。
其中,为使S1和S2中有个数大致相等的点,选取S中个点坐标的中位数来作分割点m。
具体算法实现见程序。
三、实现环境本程序在VC++环境下实现。
四、测试情况本程序可按屏幕提示进行输入操作,即相应输出如下:输入一维点集的各元素(以-1结束):5 3 96 8 15 26 -1该一维点集中最近点对为(9,8),其距离为1五、源代码#include "iostream.h"#define M 20struct cpair//表示具有最近距离的点对(d1,d2)的距离dist {float dist;float d1,d2;};int input(float s[],int n)//s[]为一维点集,n为s[]中的元素个数{cout<<"输入一维点集的各元素(以-1结束):\n";n=0;cin>>s[n];while(s[n]!=-1){n++;cin>>s[n];}return n;}float max(float s[],int b,int e)//返回s[b]到s[e]中的最大值{float m1=s[b];for(int i=b+1;i<=e;i++) if(m1<s[i]) m1=s[i];return m1;}float min(float s[],int b,int e)//返回s[b]到s[e]中的最小值{float m1=s[b];for(int i=b+1;i<=e;i++) if(m1>s[i]) m1=s[i];return m1;}//返回s[]中的具有最近距离的点对及其距离cpair cpair1(float s[],int n){cpair temp={1000,0,0};//当点集中元素的个数不足2时,返回具有无穷大的dist值(此处设为1000)的temp if(n<2) return temp;float m1=max(s,0,n-1),m2=min(s,0,n-1);float m=(m1+m2)/2;//找出点集中的中位数int j=0,k=0;//将点集中的各元素按与m的大小关系分组float s1[M],s2[M];for(int i=0;i<n;i++){if(s[i]<=m) {s1[j]=s[i];j++;}else {s2[k]=s[i];k++;}}cpair d1=cpair1(s1,j),d2=cpair1(s2,k);//递归float p=max(s1,0,j-1),q=max(s2,0,k-1);//返回s[]中的具有最近距离的点对及其距离if(d1.dist<d2.dist){if((q-p)<d1.dist){temp.dist=(q-p);temp.d1=q;temp.d2=p;return temp;}else return d1;}else{if((q-p)<d2.dist){temp.dist=(q-p);temp.d1=q;temp.d2=p;return temp;}else return d2;}}void main(){int n,m;float s[M];cpair dist;m=input(s,n);dist=cpair1(s,m);cout<<"\n 该一维点集中最近点对为("<<dist.d1<<","<<dist.d2<<"),其距离为"<<dist.dist<<endl;}II.二维问题一、问题描述与分析问题描述与分析见上。
下面仅对最接近点对中的2个点分别在S1和S2中的情形作具体分析。
二、基本算法S 中的点为平面上的点,它们都有两个坐标值x 和y 。
为了将平面上点集S 线形分割为大小大致相等的两个子集S1和S2,选取一垂直线l :x=m 来作为分割直线。
其中m 为S 中各点x 坐标的中位数。
由此将S 分割为S1={p ∈S|x (p )<=m }和S2={p ∈S|x (p )>m }。
从而使S1和S2分别位于直线l 的左侧和右侧,且S=S 1∪S2。
由于m 是S 中各点x 坐标值的中位数,因此S1和S2中的点数大致相等。
递归的在S1和S2上届最接近点对问题,分别得到S1和S2中的最小距离d1和d2。
现设d=min{d1,d2}。
若S 的最接近点对(p ,q )之间的距离小于d ,则p 和q 必分属于S1和S2。
不妨设p ∈S1,q ∈S2。
p 和q 距离直线l 的距离均小于d 。
因此,若用P1和P2分别表示直线l 的左边和右边的宽为d 的两个垂直长条。
则p ∈P1,q ∈P2,如下图所示。
在二维条件下,P1中所有点与P2中所有点构成的点对均为最接近点对的候选者。
由d 的意义可知,P2中任何两个S 中的点的距离都不小于d 。
由此可推出矩阵R 中最多只有6个S 中的点。
由此稀疏性质,对于P1中任一点p ,P2中最多只有6个点与它构成最接近点对的候选者。
因此,在分治法的合并步骤中,最多只需要检查6*n/2=3*n 个候选者。
但并不确切知道要检查哪6个点。
为解决这问题,可以将p 和P2中所有S2的点投影到垂直线l 上。
由于能与p 点一起构成最接近点对候选者的S2中的点一定在d*2d 的矩形中,所以它们在直线l 上的投影点距p 在l 上投影点的距离小于d 。
由上述分析可知,这种投影点最多有6个。
因此,若将P1和P2中所有S 中点按其y 坐标排好序,则对P1中所有点,对排好序的点列作一次扫描,就可以找出所有最接近点对的候选者。
对P1中每一点最多只要检查P2中排好序的相继6个点。
具体算法见程序。
S1S2图2 距直线l 的距离小于d 的所有点三、实现环境本程序在VC++环境下实现。
四、测试情况本程序可按屏幕提示进行输入操作,即相应输出如下:请输入点集中元素的个数:1该n值不存在最近点对,请重新输入!请输入点集中元素的个数:4请依次输入二维点集中的各点:第1个点:2 5第2个点:1 4第3个点:3 3第4个点:5 8该点集中的最近点对为(1,4)和(2,5),它们的距离为1.41421五、源代码#include "iostream.h"#include "stdio.h"#include "math.h"#define M 20struct point{float x,y;//点的x,y坐标int id;//点在数组中的标号};struct pair{point a,b;float dist;//a,b两点间的距离};//若xx为真时,对point型数组x[n]按元素的x坐标排序;否则按其y坐标排序void sort(point x[],int n,bool xx){for(int i=0;i<n;i++){for(int j=i+1;j<n;j++){if(xx==true)//按元素的x坐标排序{if(x[i].x>x[j].x){//x[i]、x[j]互换point t=x[j];x[j]=x[i];x[i]=t;//修改x[i]、x[j]的id值x[i].id=i;x[j].id=j;}}else//按元素的y坐标排序{if(x[i].y>x[j].y){//x[i]、x[j]互换point t=x[j];x[j]=x[i];x[i]=t;//修改x[i]、x[j]的id值x[i].id=i;x[j].id=j;}}}}}//输入二维点集中的各点,记录于数组x[]中void input(point x[],int n){cout<<"请依次输入二维点集中的各点:\n";for(int i=0;i<n;i++){cout<<"第"<<i+1<<"个点:";cin>>x[i].x>>x[i].y;x[i].id=i;}}float dis(point a,point b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}pair closestpair(point x[],point y[],point z[],int l,int r){pair t;if(r==l)//1点的情形{t.dist=1000;return t;}if(r-l==1)//2点的情形{t.a=x[l];t.b=x[r];t.dist=dis(x[l],x[r]);return t;}if(r-l==2)//3点的情形{float d1=dis(x[l],x[l+1]),d2=dis(x[l+1],x[r]),d3=dis(x[l],x[r]);if(d1<=d2 && d1<=d3){t.a=x[l];t.b=x[l+1];t.dist=d1;return t;}if(d2<=d3){t.a=x[l+1];t.b=x[r];t.dist=d2;return t;}else{t.a=x[l];t.b=x[r];t.dist=d3;return t;}}//多于3点的情形,用分治法int m=(l+r)/2;int f=l,g=m+1;for(int i=l;i<=r;i++){if(y[i].id>m) z[g++]=y[i];else z[f++]=y[i];}//递归求解pair best=closestpair(x,z,y,l,m);pair right=closestpair(x,z,y,m+1,r);if(right.dist<best.dist) best=right;//将距中位线l=m的距离小于dist且宽度为2*dist的点置于z[]中int k=l;for(i=l;i<=r;i++){if(abs(x[m].x-y[i].x)<best.dist) z[k++]=y[i];}//搜索z[l:k-1]for(i=l;i<k;i++){for(int j=i+1;j<k && z[j].y-z[i].y<best.dist;j++){float dp=dis(z[i],z[j]);if(dp<best.dist){t.a=z[i];t.b=z[j];t.dist=dp;return t;}}}return best;}//寻找最近点对pair cpair2(point x[],int n){int i;pair t;if(n<2) {t.dist=1000;return t;}//当元素个数不足2时,返回具有较大dist值的t sort(x,n,true);//依x坐标排序point y[M];for(i=0;i<n;i++)//将数组x[]中的点复制到数组y[]中y[i]=x[i];sort(y,n,false);//依y坐标排序point z[M];return closestpair(x,y,z,0,n-1);//计算最近点对}void main(){int n;point x[M];pair t;cout<<"请输入点集中元素的个数:";cin>>n;while(n<2){cout<<"该n值不存在最近点对,请重新输入!\n";cout<<"请输入点集中元素的个数:";cin>>n;}cout<<endl;input(x,n);t=cpair2(x,n);cout<<"\n该点集中的最近点对为("<<t.a.x<<","<<t.a.y<<")和("<<t.b.x<<","<<t.b.y<<"),\n 它们的距离为"<<t.dist<<endl;}。