HDOJ1007求最近点对

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1007

题目大意就是输入N个玩具的坐标,每个玩具作为一个点来处理,最后输出最近点对距离的一半。

题目采用分治的思想解决,首先对所有的点根据x坐标进行排序,找到所有点下标的中卫数mid,以mid点的横坐标xmid作为分割线,即分割线为x=xmid,将所有点集分为左右两个子集,递归地求两个点集的最短距离dleft和dright,设d=min{ dleft,dright},d为两边点集的最小距离。则所有点的最小距离可能为d,也可能小于d。所以我们需要找横跨分割线的点对有没有距离小于d的两个点。横跨分割线距离小于d的两个点,只有可能在x=xmid-d和x=xmid+d两条直线围成的区域中,我们在这个区域中寻找距离小于d的点。我们把这个区域定义为候选区域。

对于候选区域内所有的点按照y坐标排序,遍历每个点,每个点都与它后面的点计算距离,这样可以知道在候选区域中,有没有距离小于d的点对。但是在最坏的情况下,所有的点都在候选区域中,这个操作将产生o(n.^2)的复杂度。我们可以减少计算距离的次数:

我们可以减少在候选区域中计算距离的次数:对于候选区域内任意一点A,作长为2d,宽为d的矩形,使得点A在矩形的底边上,则在这个矩形内最多只能有6个点,6个点的情况就是这6个点是矩形的四个顶点和x=xmid的两个交点,所以每个点最多只需要计算与后面5个点的距离就好。

代码如下:

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
#define DOUBLE_MAX 9999999
#define min(a,b) (a)<=(b)?(a):(b)
//#define DEBUG
class Point{
public:
	double x,y;
};
Point toy[100005];
Point temp[100005];
int N;//case个数

class Solution{
public:
	double solve();
private:
	double closest(int left,int right);//toy[left]-toy[right]距离最近的点
	void sortWithX(int left,int right);
	double distance(double x1,double y1,double x2,double y2){
		return sqrt(  (x1-x2)*(x1-x2) +(y1-y2)*(y1-y2) );
	}
	void mergeY(int left,int right);//仿照归并排序的merge,对所有的点按照y坐标排序
};

double Solution::solve(){
	sortWithX(0,N-1);
#ifdef DEBUG
	//cout<<"按照x排序后的点:"<<endl;
	//for(int i=0;i<N;i++) cout<<toy[i].x<<" "<<toy[i].y<<endl;
#endif

	return closest(0,N-1);
	
}

double Solution::closest(int left,int right){
	double d;
#ifdef DEBUG
	//cout<<"invoke closest,left="<<left<<"right="<<right<<endl;
#endif
	if(left==right) return DOUBLE_MAX;
	else if(right==left+1){
		d=distance(toy[left].x,toy[left].y,toy[right].x,toy[right].y);
		mergeY(left,right);//将所有的点按照y坐标排序,此时left到mid以及mid+1到right y坐标有序了已经
		return d;
	}

	int mid=(left+right)/2;
	double x_mid=toy[mid].x;
	double d1=closest(left,mid),d2=closest(mid+1,right);
	d=min(d1,d2);
	mergeY(left,right);
#ifdef DEBUG
	//cout<<"按照y排序前:left="<<left<<"right="<<right<<endl;
	//for(int i=left;i<=right;i++) cout<<toy[i].x<<" "<<toy[i].y<<endl;
#endif

#ifdef DEBUG
	if(left==0 && right==N){
		cout<<"按照y排序以后:left="<<left<<"right="<<right<<endl;
		for(int i=left;i<=right;i++) cout<<toy[i].x<<" "<<toy[i].y<<endl;
	}
	cout<<"按照y排序以后:left="<<left<<"right="<<right<<endl;
	for(int i=left;i<=right;i++) cout<<toy[i].x<<" "<<toy[i].y<<endl;
#endif
	
	//把在长为2d区域中的点加入temp
	int k=0;
	for(int i=left;i<=right;i++) if(fabs(toy[i].x-x_mid)<=d) temp[k++]=toy[i];//temp[0]-temp[k-1]为在区域中的点
	for(int i=0;i<k;i++){
		for(int j=i+1;j<=i+5 && j<k;j++){
			double d2=distance(temp[i].x,temp[i].y,temp[j].x,temp[j].y);
			if(d2<d) d=d2;
		}
	}
	return d;
}

void Solution::mergeY(int left,int right){
	int mid=(left+right)/2;
	int i=left,j=mid+1,k=0;
	while(i<=mid && j<=right){
		if(toy[i].y<=toy[j].y) temp[k++]=toy[i++];
		else temp[k++]=toy[j++];
	}
	while(i<=mid) temp[k++]=toy[i++];
	while(j<=right) temp[k++]=toy[j++];
	for(int i1=0,i2=left;i1<k;) toy[i2++]=temp[i1++];
}
void Solution::sortWithX(int left,int right){
	if(left>=right) return;
	else{
		int mid=(left+right)/2;
		sortWithX(left,mid);
		sortWithX(mid+1,right);

		//merge
		int i=left,j=mid+1;
		int k=0;
		while(i<=mid && j<=right){
			if(toy[i].x<=toy[j].x) temp[k++]=toy[i++];
			else temp[k++]=toy[j++];
		}
		while(i<=mid){
			temp[k++]=toy[i++];
		}
		while(j<=right){
			temp[k++]=toy[j++];
		}
		for(int i1=0,i2=left;i1<k;){
			toy[i2]=temp[i1];
			i1++;i2++;
		}
	}
}

int main(){
	Solution s;
	while(scanf("%d",&N) && N!=0){
		for(int i=0;i<N;i++) scanf("%lf%lf",&toy[i].x,&toy[i].y);
		double min_dis=s.solve();
		//cout<<"最短距离:"<<min_dis<<endl;
		printf("%.2f\n",min_dis/2);
	}
}

候选区域内的点需要按照y排序,这里我们为了减少时间复杂度,在分治递归求完两边的最短距离以后,直接按照归并排序中的合并操作对所有点按照y进行排序就好。

例如,输入四个点:(-1,3)(-1,0),(0,2),(1,1)四个点,运行上述代码,首先递归求(-1,3)和(-1,0)的最小距离,由于只有两个点,直接计算两点的距离,并且按照归并排序中的合并操作,对于这两个点进行了排序。后面两个点同理。对于四个点来说,递归求解左右两个点的最短距离以后,左右两个点集都已经y坐标有序,这个时候按照归并排序的合并操作,继续合并,所有点对y坐标有序,这个时候再选出在候选区域中的点,在候选区域中找是否有距离小于d即可。递归调用closest有logn次,每次的调用进行归并排序的合并操作,产生o(n)的复杂度,整个算法的复杂度为o(nlogn)。

猜你喜欢

转载自blog.csdn.net/LOVETEDA/article/details/83781397