最近点对问题 分治法证明及两种分治法

问题描述

对于平面上给定的N个点,给出所有点对的最短距离,即,输入是平面上的N个点,输出是N点中具有最短距离的两点。

暴力法

暴力法的思路相对简单,即枚举所有可能的配对情况,然后一一比对,找到距离最小的点,一共有 Cn2 种组合,也就是 n*(n-1)/2种,总的时间复杂度是O(n^2),因为时间开销过于昂贵,我们寻求别的方法

分治法

问题分割策略:排序,取中位数

如果像快速排序,分割的策略与数据有关,那么算法便会退化,不能达到稳定的O(nlogn)

但是如果效仿归并排序,每次选取中点(中位数)进行分割,那么可以使得递归树的深度稳定的控制在log(n)

这里对点做预处理,按照x升序,x相同则y升序排序,这样我们每次选取中间下标的点,都能尽量地将点分割成几何上的两半

在这里插入图片描述

子问题存在分析:

问题是选取两个点,p1,p2,使得他们距离最短,子问题存在于以下空间

  • p1 p2 同时位于左边点集
  • p1 p2 同时位于右边点集
  • p1 p2 一个在左侧一个在右侧

对于子问题1和2,我们可以通过递归直接解决,难点就在于解决子问题3,即两点在两边的情况

子问题3求解 缩小空间:

想起来归并是要【合并】的,如何有效利用递归得到的答案呢?
注意这里如果要一一配对找两边点求距离,那么合并效率还是O(n^2)

递归得到两边点子集的最近点对距离,取最小值d,我们可以借此距离d来缩小我们查找的空间

从中间点向外扩散,如果点与中间点的x左边之差,大于d,那么这个点及其往后的点,都不可能是最优的答案,缩小了查找空间

在这里插入图片描述
缩小查找空间,其实是不稳定的,可能查找空间和原来一样,所以我们不能暴力枚举查找空间内的点,下面引入一个结论,来帮助我们快速查找

结论:点目标出没区域确定

结论:对于左边区域的每一点p,要想在右边区域找到一点p’使得 pp’距离<=d,这样的点p’必定存在于【以p的y座标为中心,上下延展d形成的d*2d的子矩形中】

证明:
假设极限情况,p在左右边线上,以p为中心半径为d画圆,半圆区域内,都是距p的距离小于d的,半圆是矩形的真子集,故目标点一定存在于矩形中

而且因为右侧的点具有稀疏性,即两两之间距离大于等于d, d*2d的矩形区域,矩形区域内最多存在6个点(如果圆形区域则是四个)

在这里插入图片描述
我们遍历左边区域的所有点,划分出d*2d的矩形区域,然后检查6个点,更新答案即可,可是如何用短时间找到d*2d的矩形区域? 想到矩形区域的划分和y有关,那么应该是利用y座标的特性了!

y的划分

对左右的子区域的所有点按照y升序排序(左右分开排序,不是混合在一起),在指针i升序遍历左边的所有点的时候,设置指针h在右边的点中向后查找,找到第一个不矮于iy座标d高度的点h(即hi点高度差小于d),从h往后找6个点即可

(因为点y座标递增,那么对应的矩形区域,下边界也递增,那么上一次找过的下界点(h指针),就不用再从头开始找了,直接在上一次的基础上找就行了)
在这里插入图片描述

合并代价O(n)的证明

因为左右点集都是升序,这表明,i++之后,下一个i点的d*2d矩形的下边界一定会提升,而h指针随着做出更新即可,因为i点矩形的下边界是递增的,h指针不走回头路

h指针最多从0增加到右边区域点的个数,最多n/2次,所以遍历左边点集,h迭代的次数,均摊到每一次i迭代,就是均摊一次,O(1),而查找矩形区域总共是6个点,也是常数时间可以完成,所以总的复杂度仍然是O(n)

分治法的改进:

因为每次递归之后,还要对左右按d划分的区域排序,总复杂度是O(2*n/2log(n/2)), 即合并代价,不是线性效率,总体复杂度超过O(nlong(n))

那么如何优化呢?
效仿归并排序,每次二分递归之后,我们对数组归并,因为递归排好了左右子区间,这样归并后,数组就是对y有序的了,不用消耗额外的时间再排序了,可以使合并子问题的代价变为O(n)级别

代码

代码的前半部分是一些帮助函数,后面两个函数才是分治法和归并分治法的代码

#include <bits/stdc++.h>

using namespace std;

typedef struct p
{
	int x, y;
	p(){}
	p(int X, int Y):x(X),y(Y){}	
}p;

/*
浮点最小函数,防止默认min的int形参截断 
*/
double lfmin(double a, double b)
{
	return (a<b)?(a):(b);
}

/*
比较函数,排序用,x升序,x相同则y升序
param p1 : 第一个点
param p2 : 第二个点 
return   : p1 前于 p2 ? 
*/
bool cmpx(const p& p1, const p& p2)
{
	if(p1.x==p2.x) return p1.y<p2.y;
	return p1.x<p2.x;
}

/*
比较函数,排序用,则y升序,归并用 
param p1 : 第一个点
param p2 : 第二个点 
return   : p1 前于 p2 ? 
*/
bool cmpy(const p& p1, const p& p2)
{
	return p1.y<p2.y;
}

/*
求解两点欧氏距离 
param p1 : 第一个点
param p2 : 第二个点 
return   : 距离,浮点数 
*/
double dis(const p& p1, const p& p2)
{
	return sqrt((double)(p1.x-p2.x)*(p1.x-p2.x)+(double)(p1.y-p2.y)*(p1.y-p2.y));
}

/*
求两点水平距离 
param p1 : 第一个点
param p2 : 第二个点 
return   : 水平距离,浮点数 
*/
double disX(const p& p1, const p& p2)
{
	double ans = (double)p1.x - (double)p2.x;
	if(ans<0) return ans*-1;
	return ans;
}

/*
暴力求解最近点对
param points : 点的数组
return       : 最近点对距离 
*/ 
double cp(vector<p>& points)
{
	double ans = (double)INT_MAX;
	for(int i=0; i<points.size(); i++)
		for(int j=i+1; j<points.size(); j++)
			ans = lfmin(ans, dis(points[i], points[j]));
	return ans;
}

/*
分治求解最近点对,复杂度O(nlog(n)log(n)) 
param points : 点的数组
param l      : 点数组左端点
param r      : 点数组右端点 
return       : 最近点对距离 
explain      : 区间[l, r]左闭右闭 
*/ 
double cp(vector<p>& points, int l, int r)
{
	if(l>=r) return (double)INT_MAX;
	if(l+1==r) return dis(points[l], points[r]);
	int mid=(l+r)/2, le=mid, ri=mid, h=0;
	double d=lfmin(cp(points, l, mid), cp(points, mid+1, r)), ans=d;
	vector<p> ll, rr;
	while(le>=l && disX(points[mid], points[le])<=d) ll.push_back(points[le]), le--;
	while(ri<=r && disX(points[mid], points[ri])<=d) rr.push_back(points[ri]), ri++;
	sort(ll.begin(), ll.end(), cmpy), sort(rr.begin(), rr.end(), cmpy);
	for(int i=0; i<ll.size(); i++)
	{
		while(h<rr.size() && rr[h].y+d<ll[i].y) h++; h=min((int)rr.size(), h);
		for(int j=h; j<min((int)rr.size(), h+6); j++)
			if(!eqfunc()(ll[i], rr[j])) ans=lfmin(ans, dis(ll[i], rr[j])); 
	}
	return lfmin(ans, d);
}

/*
分治+归并求解最近点对,复杂度O(nlog(n)) 
param points : 点的数组
param l      : 点数组左端点
param r      : 点数组右端点 
return       : 最近点对距离 
explain      : 区间[l, r]左闭右闭 
*/ 
double cpm(vector<p>& points, int l, int r)
{
	if(l>=r) return (double)INT_MAX;
	if(l+1==r) 
	{
		if(cmpy(points[r], points[l])) swap(points[l], points[r]);
		return dis(points[l], points[r]);
	}
	int mid=(l+r)/2, le=mid, ri=mid, h=0;
	p midp = points[mid];
	double d=lfmin(cpm(points, l, mid), cpm(points, mid+1, r)), ans=d;
	inplace_merge(points.begin()+l, points.begin()+mid+1, points.begin()+r+1, cmpy);
	vector<p> ll, rr;
	for(int i=l; i<=r; i++)
	{
		if(midp.x>=points[i].x && disX(midp, points[i])<=d) ll.push_back(points[i]);
		if(midp.x<=points[i].x && disX(midp, points[i])<=d) rr.push_back(points[i]);
	}
	for(int i=0; i<ll.size(); i++)
	{
		while(h<rr.size() && rr[h].y+d<ll[i].y) h++; h=min((int)rr.size(), h);
		for(int j=h; j<min((int)rr.size(), h+6); j++)
			if(!eqfunc()(ll[i], rr[j])) ans=lfmin(ans, dis(ll[i], rr[j])); 
	}
	return lfmin(ans, d);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章