编程之美: 第二章 数字之魅 2.11寻找最近点对

/*
寻找最近点对:
分析与解法:
从简到难:
先看一位的情况:在一个包含N个数的数组中,如何快速找出N个数中两两差值的最小值?

解法1:
数组中总共包含N个数,我们把他们两两之间的差值求出来,就可以得出最小值了。时间复杂度为O(n^2)
扩展到二维,相当于枚举任意两个点,然后记录距离最近的点对。

解法2:
如果数组有序,找出最小的差值就很容易了。可以用O(NlogN)的算法进行排序,排序完成后,找出最小差值只需要O(n)的时间
但是不能推广到二维,因为距离最近的点不能保证是映射到某条直线之后紧靠着的两个点。例如点A和点C的距离最近,但他们在X轴上的投影点不是相邻的

解法3:
用数组的中间值k把数组分成left,right两部分,小于k的数位left部分,其他为right部分,那么这个最小差值要么来自left部分,要么来自right部分
要么是left中最大的数和right中最小的数的差值。借用分治思想,时间复杂度为O(NlogN)

根据水平方向的坐标把平面上的N个点分成两部分,希望这两个部分点数的个数差不多。假设分别求出Left和Right两部分中距离最近的点对之最短距离分别为
MinDist(Left)和MinDist(Right),还有一种情况没有考虑,一个点来自与left部分,另一个点来自于right部分。只考虑有可能成为最近点对的候选对。
如果Left和Right之间的点对距离超过MDist = min(MinDist(Left),MinDist(Right)),则它们必定不是最近点对。

通过直线x = M将所有的点分成x<M和x>M两部分,分别求出两部分的最近点对之后,需要考虑点对CD。即只需要考虑x = M -Mdist到x = M + Mdist之间这个带状区域
内的最小点对,然后再跟MDist比较就可以了。在计算带状区域的最小点对时,可以按照Y坐标,对带状区域内的顶点进行排序,如果一个点对的距离小于MDist,
那么他们一定在一个MDist*(2*Mdist)的区域内,而在左右两个Mdist*MDist正方形区域内,最多都只能有4个点。如果超过4个点,那么这个正方形区域内至少存在
一个点对的距离小于Mdist,这跟x < M和x > M两个部分的最近点对距离分别是MinDist(Left)和MinDist(Right)矛盾。
因此一个MDist*(2*Mdist)的区域内最多有8个点,对于任意一个带状区域内的顶点,只要考察它与按Y坐标排序且紧挨着的7个点之间的距离就可以了。
可以用归并排序法将带状区域的点按Y坐标排序,归并排序的过程与计算最近点对的算法结合在一起。

//输入:
//6
//5 6 8 3 7 9
//输出:
//1

输入:
5
-2 2 
-1 1
1 0
2 2
3 5
输出:
1.4
*/
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
const int MAXSIZE = 10000;
const int INF = 0x7fffffff;

typedef struct Point
{
	double _dX,_dY;
	int _iIndex;
	Point():_dX(0.0),_dY(0.0),_iIndex(0){}
	void set(double x,double y)
	{
		_dX = x;
		_dY = y;
	}
}Point;
Point xPointArr[MAXSIZE];
Point yPointArr[MAXSIZE];

double min(double a,double b)
{
	return a < b ? a : b;
}

bool cmpx(const Point& point1,const Point& point2) 
{
	return point1._dX < point2._dX;
}

bool cmpy(const Point& point1,const Point& point2)
{
	return point1._dY < point2._dY;
}

double distance(const Point& point1,const Point& point2)
{
	return sqrt( (point1._dX - point2._dX)*(point1._dX - point2._dX) + (point1._dY - point2._dY)*(point1._dY - point2._dY)  );
}

double minDistance(int low,int high,Point* pPoint)//不懂
{
	double dRet = INF;
	if(high - low <= 2)//当high - low = 2,例如,high = 3,low = 1,此时就把里面所有点的距离计算一遍,也就是只有3个节点或2个节点,直接计算出谁是最小值
	{
		for(int i = low ; i < high ; i++)
		{
			for(int j = i + 1 ; j <= high ; j++)
			{
				double dDis = distance(xPointArr[i],xPointArr[j]);
				if(dDis < dRet)
				{
					dRet = dDis;
				}
			}
		}
		return dRet;
	}
	int mid = (low + high) / 2;//划分
	Point* yL = new Point[mid - low + 1];//这边是均分点数组
	Point* yR = new Point[high - mid];//?
	int i,j,k,ind;
	for(i = 0, j = 0,k = 0 ; i <= high - low ; i++)//均分点数组
	{
		if(pPoint[i]._iIndex <= mid)//mid时中间下标,起始是将按照Y坐标排序的点,一半划分到左边,一半划分到右边
		{
			yL[j++] = pPoint[i];
		}
		else
		{
			yR[k++] = pPoint[i];
		}
	}
	double left_min = minDistance(low,mid,yL);//递归求解最小点对距离
	double right_min = minDistance(mid+1,high,yR);
	dRet = min(left_min,right_min);
	double dLine = xPointArr[mid]._dX;//使用原来中间节点的横坐标
	for(i = 0 , ind = 0 ; i <= high - low ; i++)
	{
		if(fabs(pPoint[i]._dX - dLine) < dRet)//比较某个结点数组距离中间线的距离值
		{
			pPoint[ind++] = pPoint[i];//?收集距离集中线距离小于d的节点,保存到yPointArr中
		}
	}
	for(i = 0 ; i < ind - 1 ; i++)//比较某个顶点与其余7个顶点距离
	{
		for(j = i + 1 ; j <= i+7 && j < ind; j++)
		{
			double dTemp = distance(pPoint[i],pPoint[j]);
			if(dTemp < dRet)
			{
				dRet = dTemp;
			}
		}
	}
	delete[] yL;
	delete[] yR;
	return dRet;
}
//
//float minDifference(float* pArr,int iLen)
//{
//	if(!pArr || iLen <= 0)
//	{
//		return INF*1.0;
//	}
//	float dRet = abs(pArr[0] - pArr[1]);
//	for(int i = 2 ; i < iLen ; i++)
//	{
//		if(abs(pArr[i-1] - pArr[i]) < dRet)
//		{
//			dRet = abs(pArr[i-1] - pArr[i]);
//		}
//	}
//	return dRet;
//}

//void process()
//{
//	int n;
//	while(EOF != scanf("%d",&n))
//	{
//		float iArr[MAXSIZE];
//		for(int i = 0 ; i < n ; i++)
//		{
//			scanf("%f",&iArr[i]);
//		}
//		sort(iArr,iArr+n);
//		printf("%.2f\n",minDifference(iArr,n));
//	}
//}

void process2()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		memset(xPointArr,NULL,sizeof(xPointArr));
		memset(yPointArr,NULL,sizeof(yPointArr));
		for(int i = 0 ; i < n ; i++)
		{
			scanf("%lf %lf",&xPointArr[i]._dX,&xPointArr[i]._dY);
		}
		sort(xPointArr,xPointArr+n,cmpx);
		for(int i = 0 ; i < n ; i++)
		{
			xPointArr[i]._iIndex = i;//设定下标为数组的排序顺序
		}
		memcpy(yPointArr,xPointArr,sizeof(Point)*n);//拷贝按照横坐标排序的点数组到纵坐标排序的数组
		sort(yPointArr,yPointArr+n,cmpy);//等于运用了两个排序原则,按照Y坐标来排序
		//printf("%.2lf\n",minDistance(0,n-1,yPointArr));//既然传的是n-1,表示n-1其实是用到的
	}
}

int main(int argc,char* argv[])
{
	process2();
	getchar();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值