【算法】最接近点对问题(递归与分治)
题目
【问题描述】给定二维平面上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的最接近点对的距离以及最接近点对。
如下图所示,子集合S1点以蓝色表示,子集合S2以绿色表示。蓝色连线为子集合S1最接近点对间的线段;绿色连线为子集合S2最接近点对间的线段;紫色连线为集合S最接近点对间的线段。
分析
主要参考:参考
修改部分:
为了求出S1和S2的最接近点对,对排好序后的x数组进行的拷贝,输出要求保留一位小数,所以使用了%.1f
,要求x较小的先输出,所以在每一个输出前都进行了比较,这里需要注意m本身要分配到S1里,为了方便计算S2的长度所以并没有在循环语句里声明j,而是在循环外,使之一直保留。
代码
#include<iostream>
#include<time.h>
#include<cmath>
#include<cstdio>
using namespace std;
// 如果两点之间垂直距离大于d,那么这两点间距必然大于d
const int M = 50;
int sm=0;
//用类表示排好序的点
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;//坐标
float x,y;
} ;
template<class Type>
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<class Type>
void mergesort(Type a[],Type b[],int left,int right)
{
if(left<right)
{
int i = (left + right)/2;
mergesort(a,b,left,i);
mergesort(a,b,i+1,right);
merge(a,b,left,i,right);//合并到数组b
copy(a,b,left,right);//复制回数组a
}
}
template<class Type>
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);
}
void close(pointx x[],pointy y[],pointy z[],int l,int r,pointx &a,pointx &b,float &d)
{
if(r-l==1)//两个点
{
a = x[l];
b = x[r];
d = dis(x[l],x[r]);
return;
}
if(r-l==2)
{
//3点
float d1 = dis(x[l],x[l+1]);
float d2 = dis(x[l+1],x[r]);
float d3 = dis(x[l],x[r]);
if(d1<=d2 &&d1<=d3)
{
a=x[l];
b = x[l+1];
d = d1;
return;
}
if(d2<=d3)
{
a = x[l+1];
b = x[r];
d = d2;
}
else
{
a = x[l];
b = x[r];
d = d3;
}
return;
}
//多于3
int m = (l+r)/2;
int f=l,g=m+1;
//分割
for(int i=l;i<=r;i++)
{
if(y[i].p>m)z[g++]=y[i];
else z[f++] = y[i];
}
close(x,z,y,l,m,a,b,d);
float dr;
pointx ar,br;
close(x,z,y,m+1,r,ar,br,dr);
if(dr<d)
{
a=ar;
b=br;
d=dr;
}
merge(z,y,l,m,r);
sm=m;
int k=l;//把区域d中的点置于z数组
for(int i=l;i<=r;i++)
{
if(fabs(x[m].x-y[i].x)<d)z[k++] = y[i];
}
//搜索z[l:k-1]
for(int i=l;i<k;i++)
{
for(int j=i+1;j<k&&z[j].y-z[i].y<d;j++)
{
float dp = dis(z[i],z[j]);
if(dp<d)
{
d=dp;
a=x[z[i].p];
b=x[z[j].p];
}
}
}
}
bool cpair(pointx x[],int n,pointx &a,pointx &b,float &d)
{
if(n<2)return false;
pointx *tempx = new pointx[n];
mergesort(x,tempx,0,n-1);
pointy* y = new pointy[n];
for(int i=0;i<n;i++)
{
y[i].p=i;
y[i].x=x[i].x;
y[i].y=x[i].y;
}
pointy *tempy = new pointy[n];
mergesort(y,tempy,0,n-1);
pointy *z = new pointy[n];
close(x,y,z,0,n-1,a,b,d);
delete []y;
delete []z;
delete []tempx;
delete []tempy;
return true;
}
template<typename Type>
void copy(Type a[],Type b[],int l,int r)
{
for(int i=l;i<=r;i++)a[i]=b[i];
}
int main()
{
int len;
cin >> len;
pointx x[M];
pointx c[M];
for(int i=0;i<len;i++)
{
float temp;
cin >> temp;
x[i].id = i;
x[i].x =temp;
cin >> temp;
x[i].y = temp;
}
copy(c,x,0,len);
pointx a;
pointx b;
float d;
pointx s1[M];
pointx s2[M];
pointx a1;
pointx b1;
float d1;
pointx a2;
pointx b2;
float d2;
cpair(x,len,a,b,d);
//比较简单的想法是剩下的两个数组再用一遍
// cout << sm << endl;
int i;
for(i=0;i<=sm;i++)
{
s1[i].id=x[i].id;
s1[i].x=x[i].x;
s1[i].y=x[i].y;
}
int j;
for(j=0;i<len;i++,j++)
{
s2[j].id=x[i].id;
s2[j].x=x[i].x;
s2[j].y=x[i].y;
}
/*
for(i=0;i<j;i++,j++)
{
s2[i].id=s2[i+1].id;
s2[i].x=s2[i+1].x;
s2[i].y=s2[i+1].y;
}
*/
cpair(s1,sm+1,a1,b1,d1);
printf("%.1f\n",d1);
if(a1.x>b1.x)
{
float tmp;
tmp = a1.x;
a1.x = b1.x;
b1.x = tmp;
tmp=a1.y;
a1.y=b1.y;
b1.y=tmp;
}
printf("%.1f %.1f\n%.1f %.1f\n",a1.x,a1.y,b1.x,b1.y);
cpair(s2,j,a2,b2,d2);
printf("%.1f\n",d2);
if(a2.x>b2.x)
{
float tmp;
tmp = a2.x;
a2.x = b2.x;
b2.x = tmp;
tmp=a2.y;
a2.y=b2.y;
b2.y=tmp;
}
printf("%.1f %.1f\n%.1f %.1f\n",a2.x,a2.y,b2.x,b2.y);
/* for(int i=0;i<j;i++)
{
cout << s2[i].x << ' ' << s2[i].y << endl;
}*/
printf("%.1f\n",d);
if(a.x>b.x)
{
float tmp;
tmp = a.x;
a.x = b.x;
b.x = tmp;
tmp=a.y;
a.y=b.y;
b.y=tmp;
}
printf("%.1f %.1f\n%.1f %.1f\n",a.x,a.y,b.x,b.y);
}
小结
一维其实还好理解,而从一维到二维的推理,最关键的是利用鸽舍原理推出最多6个点,不然很难处理,这题对输出的要求很多,拷贝数组时也出过很多错,做的心烦意乱,冷静下来反而很好分析了。