详解平面点集最近点对问题

详解平面点集最近点对问题

设平面上有点集 P P P,包含n个点,我们希望在这n个点中找到一对点,使其相互之间的距离最短。

如果采用蛮力法,即计算每一对点的距离之后,比较所有点对的距离。蛮力法的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

那么,能不能找到一种时间复杂度更低的方法?这里,分治策略会让时间复杂度降低到 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

  • 分(Divide):将点集P依据横坐标分成左右两个子集, P L P_L PL P R P_R PR
  • 治(Conquer):分别计算 P L P_L PL P R P_R PR两个点集的最近点对 k 1 k_1 k1, k 2 k_2 k2
  • 合(Combine):考虑 P L P_L PL P R P_R PR两个点集交界处的最近点对,即交界处可能有比 k 1 k_1 k1 k 2 k_2 k2距离更近的两个点
    在这里插入图片描述

显然,分和治的部分比较容易实现,本文将着重讨论“合”的实现。令 k = min ⁡ ( k 1 , k 2 ) k=\min(k_1,k_2) k=min(k1,k2),因为我们的目标是寻找是否在边界线周围是否有距离小于 k k k的点对,所以我们在边界线左右分别划分一个宽为 k k k的区域,仅考虑在这个范围内的点(下图红色区域)。

在这里插入图片描述

划分完边界附近的区域后,我们开始考虑“合”,即寻找比 k 1 k_1 k1 k 2 k_2 k2距离更近的两个点。最容易的想到的是,通过蛮力算法,我们把红色区域里的每一对点的距离全部计算出来,然后比较得到最短距离。但是这种方法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),并没有实质上降低时间复杂度。

蛮力算法的时间复杂度为什么是 O ( n 2 ) O(n^2) O(n2)?是因为我们将每一个点与红色区域里的所有点计算距离,所以我们改进算法的思路是,能不能让每一个点仅与有限个点计算距离,从而使时间复杂度降低到 O ( n ) O(n) O(n)

现在我们考虑一个问题,对于下图中的红色区域,由上述划分可知,每个正方形区域中任意两点之间的距离不小于 k k k,那么两个正方形区域中最多可以容纳多少点?

在这里插入图片描述

如下图所示,最多只能容纳8个点,8个点分布在两个正方形的四角。

在这里插入图片描述

也就是说,在沿纵轴长为 k k k的划分的红色区域内,最多有八个点,同时,因为我们的目标是找到跨越边界线的、距离小于 k k k的两点,所以跨越边界线的两点之间纵坐标的差值小于等于 k k k。基于这两点原因,我们可以很清晰地看到,一个点仅与纵坐标与它相近的7个点计算距离即可,没有必要与所有点之间计算距离。我们可以得到如下设计思路:

  1. 将红色区域内的点按纵坐标从小到大排序
  2. 对于区域内的每一个点,只计算它与序号在它之后的7个点之间的距离并与当前最短距离比较

我们分析一下上述算法的时间复杂度:

  1. 按横坐标排序 P P P中的点需要 O ( n log ⁡ n ) O(n\log n) O(nlogn)时间,注意这个排序是预排序
  2. 由于已经排序过,划分子集需要 Θ ( 1 ) \Theta(1) Θ(1)时间
  3. “合”中,排序花费 O ( n log ⁡ n ) O(n\log n) O(nlogn)时间,每个点最多进行7次距离计算和比较,最坏情况下耗费 Θ ( n l o g n ) \Theta(nlogn) Θ(nlogn)

KaTeX parse error: Unknown column alignment: * at position 43: … \begin{array}{*̲*lr**} …

上述递推式的解为 T ( n ) = O ( n log ⁡ 2 n ) T(n)=O(n\log^2n) T(n)=O(nlog2n)。每次对子问题都要进行一遍排序严重影响到时间复杂度。我们可以空间换时间,先将点集中的元素以它们的纵坐标进行一遍排序然后存储在 Y Y Y数组中,在“合”步骤中仅需在 Θ ( n ) \Theta(n) Θ(n)时间内提取并比较,这样递推式变为:

KaTeX parse error: Unknown column alignment: * at position 43: … \begin{array}{*̲*lr**} …

递推式的解为 T ( n ) = Θ ( n log ⁡ n ) T(n)=\Theta(n\log n) T(n)=Θ(nlogn)

下面给出整个算法的伪代码:

1.以x坐标增序对P中点排序
2.Y:=以y坐标增序对P中点排序
3.k:=cp(1,n)
cp(low,high)
	if((high-low+1)<=3)
		直接用蛮力算法计算k
	else
		mid:=(low+high)/2
		x0:=x(P[mid])
		k1:=cp(low,mid)
		k2:=cp(mid+1,high)
		k:=min{k1,k2}
		t:=0
		for i:=low to high //从Y中抽取T,T表示红色区域
			if(|x(Y[i])-x0|<=k)
				t:=t+1
				T[t]:=Y[i]
		k':=2k //把k'初始化为比k大的值
		for i:=1 to t-1
			for j:=i+1 to min{i+7,t}
				if(d(T[I],T[j])<k')
					k':=d(T[I],T[j])
		k:=min{k,k'}
		
			
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值