【算法】最接近点对问题(递归与分治)

【算法】最接近点对问题(递归与分治)

题目

【问题描述】给定二维平面上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最接近点对间的线段。
Figure_10.png

分析

主要参考:参考
修改部分:
为了求出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)
	 {
	 	//3float 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个点,不然很难处理,这题对输出的要求很多,拷贝数组时也出过很多错,做的心烦意乱,冷静下来反而很好分析了。

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值