最接近点对问题(分治)
Description
给定平面上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小。
Input
输入的第一行为测试样例的个数T,接下来有T个测试样例。每个测试的第一行是一个整数n( n < 10000 ),表示有n个点,接下来n行,每行两个整数X, Y表示点的坐标( |X| ≤ 1000,|Y| ≤ 1000 )。
Output
对应每个测试样例输出一行:最接近点对的距离,保留4位小数。
Sample Input
2
4
0 0
0 1
1 0
1 1
3
0 0
5 5
11 0
Sample Output
1.0000
7.0711
Author
Eapink
这道算法题其实很简单,但对于初涉及算法的同学来说还是有点难度,而且这道题是二维的最接近点对问题,建议第一次接触这道题的同学先弄明白一维情况下最接近点对问题,对于理解二维、三维情况下最接近点对问题会有一个好的基础。
一维情况下最接近点对问题
(大神可以忽略)
设直线l上有2m个点,以m为中点将l分割成两条线段dl,dr,然后求出dl和dr这两点条线段中的最小点距d1,d2,此时d=min{d1,d2},再通过计算出dl线段的中最大点与dr线段中的最小点之间的距离D,最小距离则为min{d,D}.
下面这部分是我从网上复制的一维最接近点对的源码- -
//2d10-1 一维最邻近点对问题
#include "stdafx.h"
#include <ctime>
#include <iostream>
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 <class Type>
void Swap(Type &x,Type &y);
template <class Type>
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<<endl<<"最近点对坐标为: (d1:"<<d.d1<<",d2:"<<d.d2<<")";
cout<<endl<<"这两点距离为: "<<d.d<<endl;
return 0;
}
float Random()
{
float result=rand()%10000;
return result*0.01;
}
int input(float s[])
{
int length;
cout<<"输入点的数目: ";
cin>>length;
cout<<"点集在X轴上坐标为:";
for(int i=0;i<length;i++)
{
s[i]=Random();
cout<<s[i]<<" ";
}
return length;
}
float Max(float s[],int l,int r)//返回s[]中的最大值
{
float s_max=s[l];
for(int i=l+1;i<=r;i++)
if(s_max<s[i])
s_max=s[i];
return s_max;
}
float Min(float s[],int l,int r)//返回s[]中的最小值
{
float s_min=s[l];
for(int i=l+1;i<=r;i++)
if(s_min>s[i])
s_min=s[i];
return s_min;
}
template <class Type>
void Swap(Type &x,Type &y)
{
Type temp = x;
x = y;
y = temp;
}
template <class Type>
int Partition(Type s[],Type x,int l,int r)
{
int i = l - 1,j = r + 1;
while(true)
{
while(s[++i]<x && i<r);
while(s[--j]>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<d2.d)
{
if((q-p)<d1.d)
{
min_d.d=(q-p);
min_d.d1=q;
min_d.d2=p;
return min_d;
}
else return d1;
}
else
{
if((q-p)<d2.d)
{
min_d.d=(q-p);
min_d.d1=q;
min_d.d2=p;
return min_d;
}
else return d2;
}
}
理解完一维的思路之后,再来理解二维的最接近点对问题就比较容易了。
我认为二维的思路其实本质上跟一维的思路是差不多了,一维分割成线,用分治法分别求两条线段的最小点对,二维分割成平面,同样是求分割后两个平面的最小点对。
我这道题的思路是这样的:
快排分割,当平面内剩下一点时返回MAXDIS(MAXDIS=99999),剩下两点直接计算两点间的距离,大于两点的情况下继续递归分割
然后回来比较分割后两边的最小距离dl,dr , d=min(dl,dr),设分割中点m,遍历出现x轴(m-d,m+d)范围且两点的y值相差的绝对值不超过d的两个点的距离D,求出此区域内的最小距离min{d,D}
下面贴上我这道题AC的代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<time.h>
#define MAXDIS 99999
#define N 100
class Point{
public:
//对象比较,此处没用到。
//可用于对象之间的比较,比较结果取决于X的大小。
/*
int operator<(Point a)const
{
return (x<a.x);
}
int operator>(Point a)const
{
return (x>a.x);
}
*/
double x,y;
};
//交换值
void swap(Point* p,int x,int y)
{
Point temp;
temp=p[x];
p[x]=p[y];
p[y]=temp;
}
//快速排序,只排一次
int qsort(Point p[],int l,int r)
{
//此处注意溢出,注意r-l有可能为0故+1处理
int rr=(int)rand()%(r-l+1);
//下面这一步很重要
rr+=l;
swap(p,l,rr);
int i=l,j=r+1;
while(true)
{
while(p[++i].x<p[l].x&&i<j);
while(p[--j].x>p[l].x);
if(i>=j)
break;
swap(p,i,j);
}
swap(p,l,j);
return j;
}
//取最小值
double min(double x,double y){
if(x<y)
return x;
else
return y;
}
//求两点间的距离
double DIS(Point a,Point b){
double x1,y1,x2,y2;
x1=a.x;
y1=a.y;
x2=b.x;
y2=b.y;
double dis=(y2-y1)*(y2-y1)+(x2-x1)*(x2-x1);
dis=sqrt(dis);
return dis;
}
//求最接近点对
double Cpair2(Point p[],int l,int r)
{
//如果r-l<=1(递归出口)
//>1 快排(一次)求中点后,分割成两部分
//求两边最小点对距离d。
//再求距中线距离为d的矩形内是否存在比d更小的点对距离
//返回最短点对及距离
if(r-l==0)
return MAXDIS;
else if(r-l==1)
{
return DIS(p[l],p[r]);
}
else if(r-l>1)
{
int m=qsort(p,l,r);
double dl;
double dr;
if(m==r)
{
dl=Cpair2(p,l,m);
dr=MAXDIS;
}
else{
dl=Cpair2(p,l,m);
dr=Cpair2(p,m+1,r);
}
//
double d=min(dl,dr);
//在这里死过一次,递归过程中多次new新的对象数组,没有用delete释放空间,最后造成内存泄露,大意了。
//使用完对象数组后注意要使用delete释放空间。
Point* pt=new Point[N];
int k=0;
for(int i=l;i<=r;i++)
{
if(fabs(p[i].x-p[m].x)<d)
{
pt[k++]=p[i];
}
}
for(int i=0;i<k;i++)
{
for(int j=i+1;j<k;j++)
{
if(fabs(pt[j].y-pt[i].y)<d)
{
double dis=DIS(pt[j],pt[i]);
if(dis<d)
d=dis;
}
}
}
//重点
delete[] pt;
return d;
}
}
int main(void)
{
// Point p[]=new Point[N];
srand((unsigned)time(NULL));
double result=0;
int t,i,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
Point* p=new Point[n];
for(i=0;i<n;i++)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
}
result=Cpair2(p,0,n-1);
printf("%.4f\n",result);
delete[] p;
}
return 0;
}
如果需要求出最小点对的坐标,可用一个数组保存下来。
总的来说这道题理解了的话其实你会发现很简单,理解完一维、二维后,其实你会发现三维的情况下也就是把一个三维空间分割两个立体图形。
算法的思路很重要,理解透了你就会发现这些问题的本质。做算法一定不能急躁,要冷静下来仔细分析,把思路理清了再来写代码,这对刚学习的同学非常重要。(昨天看了一个同学做棋盘覆盖这道题毫无头绪的打代码- -我的强迫症差点把我自己憋死)
如果发现有哪个地方说得不对的,麻烦帮我指出来,谢谢。