计算几何 平面最近点对 nlogn分治算法 求平面中距离最近的两点

本文全文原创 转载请注明出处

http://blog.csdn.net/lytning/article/details/25370169

平面最近点对,即平面中距离最近的两点

分治算法:

int SOLVE(int left,int right)//求解点集中区间[left,right]中的最近点对

{

    double ans; //answer

    0)    调用前的预处理:对所有点排序,以x为第一关键词y为第二关键字 , 从小到大;

    1)    将所有点按x坐标分成左右两部分;

    /*      分析当前集合[left,right]中的最近点对,有两种可能:

          1. 当前集合中的最近点对,点对的两点同属于集合[left,mid]同属于集合[mid,right]

             则ans = min(集合1中所有点的最近距离, 集合2中所有点的最近距离)

          2. 当前集合最近点对中的两点分属于不同集合:[left,mid][mid,right]

              则需要对两个集合进行合并,找出是否存在p∈[left,mid],q∈[mid,right],使得distance(p,q)小于当前ans(即步骤1中求得的ans);

    */

    2)    Mid = (left+right)/2;

          ans = min( SOLVE(left,mid), SOLVE(mid,right) );

          即:递归求解左右两部分中的最近距离,并取最小值;

          //此步骤实现上文分析中的第一种情况

    /*      


          再次进行分析

          我们将集合[left,right]用x = mid这条直线分割成两部分

          则如果画出直线l1:x=mid-ans 和 l2:x=mid+ans,显然如果有p∈[left,mid], q∈[mid,right]且distance(p,q) < ans则p,q一定在直线l1和直线l2之间,否则distance(p,q)必定大于ans。

          于是扫描出在l1和l2之间的点

    */

    3)    建立缓存数组temp[];

          for i = left TO right

          {

               如果 abs(Point[i].x - Point[mid].x) <= ans

               则向temp中加入点Point[i];

           }

    /*

            对于temp中的点,枚举求所有点中距离最近两点的距离,然后与ans比较即可。

            枚举的时候不必两两枚举。观察下图中的点p

           不难发现,若有q∈[mid,mid+ans]使得distance(p,q) < ans,则q点的位置一定在图中画出的一个2ans×ansd的矩形中。可以证明点集[mid,mid+ans]中的、矩形外的点与p点的距离一定大于ans。
           于是我们可以对temp以y为唯一关键字从小到大排序,进行枚举, 更新ans,然后在枚举时判断:一旦枚举到的点与p点y值之差大于ans,停止枚举。最后就能得到该区间的最近点对。

    */

    4)    sort(temp);

          for i = 0 TO k-1

          {

                for j = i+1 TO k-1

                    如果 temp[j].y - temp[i].y >= ans  break;

                    ans = min( ans, distance(temp[i], temp[j]) );

           }

    5)    return ans;

}


算法的时间复杂度

        由鸽巢原理,代码中第四步的枚举实际上最多只会枚举6个点,效率极高(一种蒟蒻的证明请看下方的评论)

        本算法时间复杂度为O(n log n)


代码:

//是vijos1012的ac代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

const double eps = 1e-8;
const int INF = 0x7fffffff;

int n;

struct Point
{
	double x,y;
	Point(double x=0, double y=0):x(x),y(y) {}
	bool operator < (const Point& p) const
	{
		if(x != p.x)	return x < p.x;
		else	return y < p.y;
	}
}p[200000+5],temp[200000+5];

bool cmpy(Point a, Point b)
{
	return a.y < b.y;
}

double Dis(Point a, Point b)
{
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

double Closest_Pair(int left, int right)
{
	double d = INF;
	if(left == right)
		return d;
	if(left +1 == right)
		return Dis(p[left],p[right]);
	int mid = (left+right)>>1;
	double d1 = Closest_Pair(left,mid);
	double d2 = Closest_Pair(mid,right);
	d = min(d1,d2);
	int k = 0;
	for(int i = left; i <= right; i++)
	{
		if(fabs(p[mid].x - p[i].x) <= d)
			temp[k++] = p[i];
	}
	sort(temp,temp+k,cmpy);
	for(int i = 0; i < k; i++)
	{
		for(int j = i+1; j < k && temp[j].y - temp[i].y < d; j++)
		{
			double d3 = Dis(temp[i],temp[j]);
			d = min(d,d3);
		}
	}
	return d;
}


int main()
{
	cin>>n;
	for(int i=0; i<n; i++)
	{
		double a,b;
		scanf("%lf%lf",&a,&b);
		p[i] = Point(a,b);
	}
	sort(p,p+n);
	printf("%.3f",Closest_Pair(0,n-1));
}

练习题:

vijos1012  https://vijos.org/p/1012

    裸题,平面最近点对(虽然本题暴力也能过=。=)

poj 3714   http://poj.org/problem?id=3714

    大意:给出平面中的两类点,求平面最近点对,要求该点对中的两点不是同类点


猜你喜欢

转载自blog.csdn.net/Lytning/article/details/25370169