最近点对问题(分治思想的经典应用)

今天开始复习典型算法,首先回顾了分治算法的应用。一个经典的问题是求二维空间中最近点对。如果硬性用循环解决这个问题的话,当点的数量较大时,循环的次数会以O(n2)增加,所以这不是较好的解决方法。对于这种问题,可以通过将原问题不断划分为更小的子问题进而加以解决。在《数据结构、算法与应用-----c++语言描述》一书中给出了这一问题完备的分治递归解决方案。 算法过程如下:

1、如果点的数量n较小,则直接用暴力循环求解。

2、否则将点集分为大致相等的两个部分A和B。

3、分别确定A和B中距离最近的点对,并取较小者L。

4、求A与B中的最近点对。

5、取上面二者中较小的解为最终答案。

其中的关键问题在第4步,即如何求两个子问题之间的最近点对。可以将A和B中与分界线距离小于L的点找到,如果这些点中任意两点的纵向距离小于L则计算其距离,取最小距离即为所求。

按照书上的解释,代码如下:

// NearestPointsFinal.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <math.h>
#include <vector>
#include <ctime>
#define  squaresize 1000
using namespace std;
int count1=0;
class Npoint
{
public:
	int ID;
	int x;
	int y;
};
void mysortX(vector<Npoint>& v)
{
	const size_t s=v.size();
	for(int gap=s/2;gap>0;gap/=2)
		for(int i=gap;i<s;++i)
			for(int j=i-gap;j>=0;j-=gap)
				if(v[j+gap].x<v[j].x){
					::count1++;
					Npoint temp=v[j];
					v[j]=v[j+gap];
					v[j+gap]=temp;
				}
}
void mysortY(vector<Npoint>& v)
{
	const size_t s=v.size();
	for(int gap=s/2;gap>0;gap/=2)
		for(int i=gap;i<s;++i)
			for(int j=i-gap;j>=0;j-=gap)
				if(v[j+gap].y<v[j].y){
					::count1++;
					Npoint temp=v[j];
					v[j]=v[j+gap];
					v[j+gap]=temp;
				}
}
template<typename T>
inline double dis(const T& p1,const T& p2)
{
	double dx=p1.x-p2.x;
	double dy=p1.y-p2.y;
	return sqrt(dx*dx+dy*dy);
}
void Merge(vector<Npoint>& X,vector<Npoint>& Y,int l,int m,int r)
{
	int i=l,j=m+1;
	int k=l;
	while((i<=m)&&(j<=r)){
		::count1++;
		if ((X[i].y)<(X[j].y)) { Y[k++]=X[i++];}
		else Y[k++]=X[j++];
	}
	if (i>m)		for (int q=j;q<=r;q++)
		{
			::count1++;
			Y[k++]=X[q];
		}
	else     for (int q=i;q<=m;q++)
		{
			::count1++;
			Y[k++]=X[q];
		}
};
void close(vector<Npoint>& X,vector<Npoint>& Y,vector<Npoint>& Z,
	int l,int r,Npoint& p1,Npoint& p2, double& mindis)
{
	::count1++;
	if (0==(r-l))//如果只有一个点,则最近点对为其本身//
	{
		p1=X[0],p2=X[0];
		mindis=sqrt(2.0*squaresize*squaresize);
		return; 
	}
	if (1==(r-l))//如果只有两个点,则最近点对为这两个点//
	{
		p1=X[0],p2=X[1];
		mindis=dis(X[l],X[r]);
		return ;
	}
	//////////////////////
	double d1,d2,d3,mintemp;
	if (2==(r-l))//如果有三个个点,则最近点对取较近的两个点//
	{
		d1=dis(X[l],X[l+1]);
		d2=dis(X[l+1],X[l+2]);
		d3=dis(X[l],X[l+2]);			
		mintemp=d1<d2?d1:d2;
		mintemp=mintemp<d3?mintemp:d3;
		if (mintemp==d1)
			{	p1=X[l]	,	p2=X[l+1];	}
		if (mintemp==d2)
			{	p1=X[l+1]	,	p2=X[l+2];	}
		if (mintemp==d3)
			{	p1=X[l]	,	p2=X[l+2];	}
		mindis=mintemp;
		return ;
	}
	int m=(l+r)/2;
	int f=l,	g=m+1;
	for (int i=l;i<=r;i++)
	{
		::count1++;
		if (Y[i].ID>m)
		{
			Z[g++]=Y[i];
		}
		else Z[f++]=Y[i];
	}
	close(X,Z,Y,l,m,p1,p2,mindis);
	double dis2;
	Npoint p3,p4;
	close(X,Z,Y,m+1,r,p3,p4,dis2);
	if (dis2<mindis)
	{
		p1=p3,p2=p4,mindis=dis2;
	}
	Merge(Z,Y,l,m,r);
	int k=l;
	for (int i=l;i<=r;i++)
	{
		if (fabs((double)Y[m].x-(double)Y[i].x)<=mindis) Z[k++]=Y[i];	
	}
	for (int i=l;i<k;i++)
		for (int j=i+1;j<k&&fabs((double)Z[j].y-(double)Z[i].y)<=mindis;j++)
	{
		::count1++;
		mintemp=dis(Z[i],Z[j]);
		if (mintemp<mindis)
		{
			mindis=mintemp;
			p1=X[Z[i].ID];
			p2=X[Z[j].ID];
		}
	}
};
bool closest(vector<Npoint>& X,int n,Npoint& p1,Npoint& p2, double& mindis)
{
	if (n<2)
	{
		mindis=sqrt(2.0*squaresize*squaresize);
		return false;
	}
	mysortX(X);
	vector<Npoint> Y;	
	vector<Npoint> Z;
	int i=0 ;
	for(vector<Npoint>::iterator it=X.begin() ; it!=X.end() ; it++,i++)
		{
			(*it).ID=i;
			Y.push_back((*it));
			Z.push_back((*it));
		}
	mysortY(Y);

	close(X,Y,Z,0,n-1,p1,p2,mindis);
	return true;
};
/*
pool: sum of points
low: start index of points
high: end index of points
p1&p2:the nearest points
*/
double NearestPermute(vector<Npoint>& pool,int low,int high,Npoint& p1,Npoint &p2)
{

	if (0==(high-low))//如果只有一个点,则最近点对为其本身//
	{
		p1.x=pool[low].x;p1.y=pool[low].y;p2.x=pool[high].x;p2.y=pool[high].y;
		return sqrt(2.0*squaresize*squaresize);
	}
	if (1==(high-low))//如果只有两个点,则最近点对为这两个点//
	{
		p1.x=pool[low].x;p1.y=pool[low].y;p2.x=pool[high].x;p2.y=pool[high].y;
		return dis(pool[low],pool[high]);
	}
	double mintemp=sqrt(2.0*squaresize*squaresize);
	double distemp=0.0;
	for (int index1=low;index1<=high;index1++)
		for (int index2=index1+1;index2<=high;index2++)
		{
			::count1++;
			distemp=dis(pool[index1],pool[index2]);
			if (distemp<mintemp)
			{
				mintemp=distemp;
				p1.x=pool[index1].x,p1.y=pool[index1].y;
				p2.x=pool[index2].x,p2.y=pool[index2].y;
			}
		}
		return mintemp;
}
int _tmain(int argc, _TCHAR* argv[])
{
	int sum=0;
	cout<<"enter how many points"<<endl;
	cin>>sum;
	if (sum<=0)
		return -1;

	vector<Npoint> Pool;
	Npoint temp;temp.x=0;temp.y=0;
	Pool.reserve(sum);

	for (int i=0;i<sum;i++)
		Pool.push_back(temp);
	//////////////////////////////////
	srand(time(0));
	for (int i=0;i<sum;i++)
		Pool[i].x=(int)(rand()%squaresize);
	for (int i=0;i<sum;i++)
		Pool[i].y=(int)(rand()%squaresize);
	//////////////////////////////////
	cout<<"Random points are:"<<endl; 	
	for (int i=0;i<sum;i++)
		cout<<"("<<Pool[i].x<<","<<Pool[i].y<<")"<<"  ";
	cout<<endl;
	/////////////////////////////////////////////
	Npoint p1,p2;
	double mindis;
	closest(Pool,sum,p1,p2,mindis);
	cout<<"Iteration:"<<::count1<<endl;
	cout<<"min dis:"<<mindis<<endl;
	cout<<"("<<p1.x<<","<<p1.y<<")"<<"  ";
	cout<<"("<<p2.x<<","<<p2.y<<")"<<"  ";
	cout<<endl;
	/////////
	::count1=0;
	mindis=NearestPermute(Pool,0,sum-1,p1,p2);
	cout<<"Iteration:"<<::count1<<endl;
	cout<<"min dis:"<<mindis<<endl;
	cout<<"("<<p1.x<<","<<p1.y<<")"<<"  ";
	cout<<"("<<p2.x<<","<<p2.y<<")"<<"  ";
	return 0;
}

运行结果如下图所示,上面是分治算法的迭代次数和结果,后者是暴力解法的迭代次数和结果(因为结果是求最小距离,所以距离最小情况下点对可能不同)。


但是偶尔会出现分治算法和暴击解法结果不一致的情况,还不清楚问什么,如下图所示。笔者猜想是A和B间最近点的计算有问题,还没有验证,希望有答案的同学和我联系啊。

发布了37 篇原创文章 · 获赞 85 · 访问量 52万+

猜你喜欢

转载自blog.csdn.net/xuanyuansen/article/details/7862117
今日推荐