二维空间下的最近点对查找
// space_minum.cpp : Defines the entry point for the console application. // #include <iostream> #include <math.h> using namespace std; float minu=10000000; int fff,bbb; void Insertsort(float list[],float X[],int a,int b); void Insertsort(float list[],float X[],int a,int b){ float next,tmp;//插入排序,对xy坐标组按照x坐标排序 int j; if(a>b)return; for(int x=a;x<=b;x++){ next=list[x]; tmp=X[x]; for(j=x-1;j>=a&&next<list[j];j--) {list[j+1]=list[j]; X[j+1]=X[j]; } list[j+1]=next; X[j+1]=tmp; } } float Dis(float x1,float y1,float x2,float y2){ float ans=sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); return ans; } int find_tmp(int mid,int limit,float dis,char updown,float x[]){ int i=mid; if(updown=='f'){//向前搜索 for(i;i>=limit&&x[mid]-dis<x[i];i--); return i; } else if(updown=='b'){//向后搜索 for(i;i<=limit&&x[mid]+dis>x[i];i++); return i; } } float seperate(float x[],float y[],int left,int right){ int mid=(left+right)/2; float dis=minu; if(right-left==0)return dis; else if(right-left==1) { float t=Dis(x[right],y[right],x[left],y[left]); if(t<minu){minu=t;fff=right;bbb=left;} return t; } else { float ll=seperate(x,y,left,mid); float rr=seperate(x,y,mid,right); if(ll<rr)dis=ll;else dis=rr; if(minu<dis)dis=minu;//修改为在以计算过的点中最小值,而非该划分中的最小值,提高速度 float t_mid=x[mid];//从划分元拓展dis单位 int f,b; f=find_tmp(mid,left,dis,'f',x);//向前向后搜索查找范围内元素 b=find_tmp(mid,right,dis,'b',x); //从mid-f个点依次选出点与b-mid个点计算最小值 //注:已经按照x坐标进行排序,当x[b]-x[f]中点>=dis时,f++,b从mid重新开始 float tmp=0; for(int i=f;i<mid;i++){ for(int j=mid;j<=b;j++){//修改比较条件j<=b&&j-mid<=6,原因已在文档中说明 if(x[j]-x[i]<dis) tmp =Dis(x[i],y[i],x[j],y[j]); else break; if(tmp!=0&&tmp<dis){dis=tmp;minu=tmp; //cout<<i<<' '<<fff<<' '; fff=i;bbb=j; } } } return dis; // float t=0;for(int i=f;i<=mid;i++){for(int j=mid;j<=b;j++){ // t =Dis(x[i],y[i],x[j],y[j]);if(t!=0&&t<dis)dis=t;}}return dis; } } const int max=20; int main(int argc, char* argv[]) { float x[max]; float y[max]; cout<<"输入点对数"; int n; cin>>n; cout<<"输入图中点坐标x,y"<<endl; for(int i=0;i<n;i++) cin>>x[i]>>y[i]; //for(i=0;i<n;i++) //cout<<x[i]<<' '<<y[i]; //按照x坐标排序,用于划分 Insertsort(x,y,0,n-1); //for(i=0;i<n;i++) //cout<<x[i]<<' '<<y[i]<<'|';cout<<endl; cout<<seperate(x,y,0,n-1)<<endl; cout<<'['<<x[fff]<<','<<y[fff]<<']'<<endl; cout<<'['<<x[bbb]<<','<<y[bbb]<<']'<<endl; return 0; }
通过递归划分子空间,减小问题规模。
当子问题中点数为0返回极大值;点数为1时,返回与划分点之间的距离。
在将N个点进行空间划分前,先按照x坐标进行排序,后依据x坐标采用二分法来进行划分。计算空间距离伪代码如下:
Separate(x[],y[],left,right){
if(right-left=0)return dis;//默认为一极大值
else if(right-left=1)return Dis(x[]y[],right,left)
//right,left为下标,同一个点在xy数组上下标一致
else {
ll= Separate(x[],y[],left,mid)
rr= Separate(x[],y[],mid,right)
dis=min(ll,rr);//记录最小值
f=find_pos(mid,left,dis,x)
//对以按x坐标排好序的点,从中间往左寻找在dis范围内点,返回值为下标
b= find_pos(mid,right,dis,x)
//对以按x坐标排好序的点,从中间往右寻找在dis范围内点,返回值为下标
//在f-mid中按照x递增顺序选择一个点A,找mid-b范围内按x递增的一点B,
//保证AB两点x坐标之差小于等于dis,计算在范围内符合约束条件的点对的最小值
for(int i=f;i<mid;i++)
for(int j=mid;j<=f;j++)//可以只访问排序后右侧前6个点j<=f&&j-mid<=6
{if(x[j]-x[i]<dis)
tmp =Dis(x[],y[],I,j)
else break//不符合跳出、再从f-mid范围内选择一点
if(tmp<dis)dis=tmp//修改最小值
}
}
return dis
}
因为该算法采用分治法,将问题分解为两个子问题,合并的复杂度为O(n),所以时间复杂度为:T(n)=2*T(n/2)+n
通过推倒得到算法复杂度为O(n*logn)
在算法开始前对数据有按照x轴坐标排序,采用复杂度为O(n*logn)的算法,得到最终算法复杂度为O(n*logn)。
其中,在合并过程中,因为在选择范围时以左右最小值dis选取范围,则在左侧区域选择的点,在包括中轴在内的右侧区域中选一点,保证点之间距离为dis则最多存在6个点,否则dis可进一步缩小。
如下图展示说明,鸽巢原理:
实验数据:[2,2][1.5,1.5][3.5,1][1.75,1.25]为例:(输入数据xy坐标交替输入)