【算法设计与分析】分治法求最近点对问题

GitHub项目地址:地址

一、实验目的:

(1)掌握分治法思想。
(2)学会最近点对问题求解方法。

二、内容:

  1. 对于平面上给定的N个点,给出所有点对的最短距离,即,输入是平面上的N个点,输出是N点中具有最短距离的两点。
  2. 要求随机生成N个点的平面坐标,应用蛮力法编程计算出所有点对的最短距离。
  3. 要求随机生成N个点的平面坐标,应用分治法编程计算出所有点对的最短距离。
  4. 分别对N=100000—1000000,统计算法运行时间,比较理论效率与实测效率的差异,同时对蛮力法和分治法的算法效率进行分析和比较。
  5. 如果能将算法执行过程利用图形界面输出,可获加分。

三、算法思想提示

  1. 预处理:根据输入点集S中的x轴和y轴坐标进行排序,得到X和Y,很显然此时X和Y中的点就是S中的点。

  2. 点数较少时的情形
    在这里插入图片描述

  3. 点数 ∣ S ∣ > 3 |S|>3 S>3时,将平面点集S分割成为大小大致相等的两个子集 S L S_L SL S R S_R SR,选取一个垂直线L作为分割直线,如何以最快的方法尽可能均匀平分?注意这个操作如果达到效率,将导致整个算法效率达到 θ ( n 2 ) \theta(n^2) θ(n2)
    在这里插入图片描述

  4. 两个递归调用,分别求出 S L S_L SL S R S_R SR中的最短距离为 d l d_l dl d r d_r dr

  5. d = m i n ( d l , d r ) d=min(dl, dr) d=min(dl,dr),在直线L两边分别扩展d,得到边界区域Y,Y’是区域Y中的点按照y坐标值排序后得到的点集(为什么要排序?),Y’又可分为左右两个集合 Y ’ L Y’L YL Y ’ R Y’R YR
    在这里插入图片描述

  6. 对于 Y ’ L Y’L YL中的每一点,检查Y’R中的点与它的距离,更新所获得的最近距离,注意这个步骤的算法效率,请务必做到线性效率,并在实验报告中详细解释为什么能做到线性效率?
    在这里插入图片描述

四、实验过程及结果

一:实验结果正确性展示

随机生成了10组50个点,通过蛮力法与分治法分别求解,验证程序正确性,结果如下。
蛮力法:
在这里插入图片描述
分治法:
在这里插入图片描述
小规模测试中二者测试结果一致,答案正确。

二:蛮力法求解

  1. 算法原理:
    (1) 存在N个点,那么就存在N(N - 1)/ 2 对点间的距离。穷举所有情况,选出最小值。

  2. 伪代码:

for i = 1 to N - 1
     for  j = i + 1 to N
          if ( dis(p[i], p[j]) < min )
               min = dis(p[i], p[j])
  1. 复杂度分析:
    (1) 需要遍历 N ( N − 1 ) / 2 N(N - 1) / 2 N(N1)/2种情况来找出最小值,最好最坏和平均情况的时间复杂度都为 O ( n 2 ) O(n^2) O(n2)
    (2) 需要一个临时变量用来存储最小值,所以空间复杂度为 O ( 1 ) O(1) O(1)
  2. 数据测试
    使用随机数生成,均匀分布的生成了1000~35000的数据集。为了减少数据的偶然性,每个数据量都进行了10次测试并取平均值。
    为了检验实验是否准确,将实际值将理论值进行对比(基准点为20000)。理论值计算方法如下:
    在这里插入图片描述
    最终结果如下:
    在这里插入图片描述
    在这里插入图片描述
    图像上符合 O ( n 2 ) O(n^2) O(n2) 二次曲线,并且理论值与实际值误差较小。

三:分治法求解

1. 分治法基本思路

本题思路:
分 – 将整体分为左右两个区域;
治 – 递归计算左右两区域的最短距离;
合 – 合并左右区域,并求合并后的最短距离;

对于本题而言,可以转化为:
在这里插入图片描述
(1) 分 – 将整体分为左右两个区域;
将所有点根据x坐标进行排序,取中间点。所以算法时间复杂度下限: O ( n l o g n ) O(nlogn) Onlogn

m i d = ( l + r ) / 2 mid = (l + r) / 2 mid=(l+r)/2
做到左右区域点集数目基本相同,降低数据随机性带来的影响
在这里插入图片描述
(2) 治 – 递归计算左右两区域的最短距离
子问题最小规模
在这里插入图片描述
递归调用函数,即可获取左右两区域的最短距离

(3) 合 – 合并左右区域,并求合并后的最短距离
在这里插入图片描述
问题转化为:已知左右区域各自最短距离,求合并后的最短距离。
合并之后最短点对的选择一共有三种情况 :左+右、左+左、右+右
对于左+左、右+右的情况,利用第二步中的递归调用即可获取。

所以主要问题在于,如果最短点对来自于左+右的合并操作。

解决思路:
两点必定来自于中轴线左右两侧附近,并且两点距离小于 m i n ( d 1 , d 2 ) min(d1,d2) min(d1,d2)
在这里插入图片描述 在这里插入图片描述
如左图所示,可以在中轴线附近取带状区域,其左右宽度为min(d1,d2)。在如此带状区域内,左右两点距离必定小于min(d1,d2),极限状态为一侧宽度长度。

问题转化为:从左右区域内各取一点,与d1,d2比较,取最短距离。

解决方法有如下展示三种。

2.分治 —— 部分蛮力

(1) 算法原理:
在这里插入图片描述
遍历左右带中的所有点,比较获得最短距离。

(2) 伪代码:

ans = min(d1,d2)
for i = 1 to left.size
     for j = 1 to right.size
	if ( dis(left[i], right[j]) < ans )
                   ans = dis(left[i], right[j])

(3) 复杂度分析:

对于均匀分布的大型点集,预计位于该带中的点的个数是非常少的。事实上,容易论证平均只有 O ( √ n ) O(√n) On个点是在这个带中的。
-----《数据结构与算法分析-C语言描述》P280

查询资料可知,对于均匀分布的点集而言,带中的元素个数为 √ n √n n
递推公式为:
平均情况 T ( n ) = 2 T ( n / 2 ) + ( √ n ) 2 T(n) = 2T(n/2) + (√n)^2 T(n)=2T(n/2)+(n)2
总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

对于非均匀分布的点集,最差情况带中的元素个数为n
递推公式为:
最坏情况 T ( n ) = 2 T ( n / 2 ) + ( n ) 2 T(n) = 2T(n/2) + (n)^2 T(n)=2T(n/2)+(n)2
总体时间复杂度 O ( n 2 ) O(n^2) O(n2)

(4) 数据分析:
最终结果如下:
在这里插入图片描述
在这里插入图片描述
图像上符合 O ( n l o g n ) O(nlogn) O(nlogn) 曲线,并且理论值与实际值误差较小。

显然对于最差情况仍然为 O ( n 2 ) O(n^2) O(n2),需要改进。

3.分治 —— 多趟查询

(1) 算法原理:
遍历左带内的所有点,并与右带内所有符合条件的点进行长度比较。

右侧符合条件点集筛选过程及原理:
结论:右侧点位于以左侧点为中心,上下高度d,右侧宽度d的d*2d的范围内。且右侧点的个数存在上限。

证明如下:
在这里插入图片描述 在这里插入图片描述

对于左侧的点,只有两点距离小于d才有合并时减小d的可能,所以右侧点必定在以左侧点为圆心、d为半径的圆内(上左图所示),其余点为无效点(上右图所示)。
又因为右侧区域内任意两点距离最小值为 d 2 ≥ d d2 \geq d d2d, 所以圆内两点距离大于等于半径。
在这里插入图片描述 在这里插入图片描述
因为右侧点一定位于圆心右侧,最大可覆盖区域为右半圆,又因为两点距离大于半径,所以最多可能存在5个点满足条件(上左图所示)。
在计算机中对点坐标的排序为x或y坐标排序,难以对圆形区域进行判断,所以将半圆扩展为矩形便于计算机运算。改矩形范围内最多可能存在6个点满足条件(上左图所示)。
在这里插入图片描述

将矩形分为6个等大的2/3d*1/2d的小矩形,假设存在7个点,则必定有一个矩形内有两个点。
同一小矩形内两大距离最大值为对角线,即0.8333d < d 与题意不符,所以不能有7个点。而6个点的情况如上右图所示。

综上所述,右侧点位于以左侧点为中心,上下高度d,右侧宽度d的d*2d的范围内。且右侧点的个数存在上限。

所以在查找过程中,只需要找到第一个属于左侧点对应矩形范围内的点,并之多向上查找6次,即可完成查询。

依次遍历左带中的点,并自下而上的遍历右带直到遇到第一个符合条件的点。

(2) 伪代码:

for i = 1 to left.size
     for j = 1 to right.size
		if right[j] 在相应的矩形内
        	for k = j to j + 6 and k < right.size
	             ans = min(ans, dis(left[i], right[j]))

(3) 复杂度分析:

对于均匀分布的大型点集,预计位于该带中的点的个数是非常少的。事实上,容易论证平均只有O(√n)个点是在这个带中的。
-----《数据结构与算法分析-C语言描述》P280

递推公式
平均情况 T ( n ) = 2 T ( n / 2 ) + ( √ n ) 2 T(n) = 2T(n/2) + (√n)^2 T(n)=2T(n/2)+(n)2
总体时间复杂度 O ( n l o g n ) O(nlogn) Onlogn)

最坏情况 T ( n ) = 2 T ( n / 2 ) + ( n ) 2 T(n) = 2T(n/2) + (n)^2 T(n)=2T(n/2)+(n)2
总体时间复杂度 O ( n 2 ) O(n^2) On2)

(4) 数据分析:
最终结果如下:
在这里插入图片描述
在这里插入图片描述
图像上符合 O ( n l o g n ) O(nlogn) O(nlogn) 曲线,并且理论值与实际值误差较小。

显然对于最差情况仍然为 O ( n 2 ) O(n^2) O(n2),需要改进。

4.分治 —— 一趟查询

(1)算法原理:
此算法在上一部分的基础上进行了部分改进,达到了线性的查找效率。
方法是对于左右侧的点都自下而上进行查找。因为矩形区域是固定的,所以随着左侧点的y坐标增大,矩形的最低点也是逐渐增大,右侧符合条件的第一个点的y也会逐渐增大,并不需要返回重新查找。(可以结合伪代码查看动图中指针的变化过程。具体过程在演示中展示。)

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

(2) 伪代码:

j = 0
for i = 1 to left.size
     while j < right.size and right[j].y < left[i].y - d
          j += 1
     for k = j to j + 6 and right[k].y < left[i].y + d
          ans = min(ans, dis(left[i], right[j]))

(3) 复杂度分析:

在此过程中,只需要对左右带中的点进行依次遍历,即可找到最短距离,最多比较6n次即可,达到了线性效率。

递推公式
T ( n ) = 2 T ( n / 2 ) + n T(n) = 2T(n/2) + n T(n)=2T(n/2)+n
总体时间复杂度 O ( n l o g n ) O(nlogn) Onlogn)

(4) 数据分析:
最终结果如下:
在这里插入图片描述
在这里插入图片描述
图像上符合$O(nlogn) $曲线,并且理论值与实际值误差较小。

5.问题思考

上述合并的方法是利用自下而上的遍历左右带中的点集,所以需要对带中的点以y坐标进行排序。
前提条件:左右区域内的点是依据y坐标进行排序的。
实际条件:左右区域内的点是依据x坐标进行排序的。(获取中轴线时,已排序)

所以每次递归的过程之中都需要对y进行排序。

对于均匀分布的大型点集,预计位于该带中的点的个数是非常少的。事实上,容易论证平均只有O(√n)个点是在这个带中的。
-----《数据结构与算法分析-C语言描述》P280

递推公式
平均情况 T ( n ) = 2 T ( n / 2 ) + √ n l o g √ n < 2 T ( n / 2 ) + n T(n) = 2T(n/2) + √nlog√n < 2T(n/2) + n T(n)=2T(n/2)+nlogn<2T(n/2)+n
合并效率小于 O ( n l o g n ) O(nlogn) O(nlogn),又因为一开始对x排序, O ( n l o g n ) O(nlogn) O(nlogn)为时间效率下限。
总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
最坏情况 T ( n ) = 2 T ( n / 2 ) + n l o g n T(n) = 2T(n/2) + nlogn T(n)=2T(n/2)+nlogn
总体时间复杂度 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn)

虽然最坏情况下为 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn),但相较于 O ( n 2 ) O(n^2) O(n2)有较大提升。

为了解决这一问题,引入以下方法。

解决方案:
我们将保留两个表。一个按照x坐标排序,一个按照y坐标排序,成为P和Q。Pl和Ql传递给左半部分递归调用,Pr和Qr传递给右半部分递归调用。一旦分割线已知,我们依序转到Q,把每一个元素放入相应的Ql或Qr。容易看出,Ql和Qr将自动地按照y坐标排序。当递归调用返回时,我们扫描Q表并删除其x坐标不在带内的所有的点。此时Q只含有带中的点,而这些点保证是按照它们的y坐标排序的。
-----《数据结构与算法分析-C语言描述》P281

递推公式: T ( n ) = 2 T ( n / 2 ) + n T(n) = 2T(n/2) + n T(n)=2T(n/2)+n
总体时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

6.综合分析

在这里插入图片描述
显然,分治法的时间效率是要远远优于蛮力法。

每一次的优化,都对于时间效率有着小幅度的提升。

  • 13
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
最近问题是指在平面上给定N个其中距离最近的两个之间的距离。分治法是一种常用的解决最近问题算法分治法的基本思想是将问题分解成多个子问题,然后递归地解决这些子问题,最后将子问题的解合并起来得到原问题的解。对于最近问题,我们可以采用以下的分治算法: 1. 将所有按照横坐标从小到大排序,然后将它们划分为两部分,分别处理左右两个部分。 2. 对于左右两个部分,分别递归最近对距离。 3. 将左右两个部分中距离最近对的距离记为d。 4. 扫描所有横坐标在中间区域内的,按照纵坐标从小到大排序。对于每个,只需考虑它与纵坐标差不超过d的之间的距离,因为这些是可能成为最近对的候选者。 5. 对于每个,只需要考虑它与后面7个之间的距离即可,因为如果有距离更近的,它们之间的距离一定小于d。 6. 扫描完中间区域内的所有后,最近对的距离就是左右两个部分中的最小值和中间区域内的最小值中的较小值。 下面是该算法的伪代码: ``` function closest_pair(points): // 按照横坐标排序 sort(points, key=lambda p: p.x) // 递归终止条件 if len(points) <= 3: return brute_force(points) // 解左右两个部分的最近对距离 mid = len(points) // 2 left_points = points[:mid] right_points = points[mid:] left_min_dist = closest_pair(left_points) right_min_dist = closest_pair(right_points) // 解中间区域的最近对距离 min_dist = min(left_min_dist, right_min_dist) strip_points = [p for p in points if abs(p.x - points[mid].x) < min_dist] strip_points.sort(key=lambda p: p.y) for i in range(len(strip_points)): for j in range(i+1, min(i+8, len(strip_points))): dist = distance(strip_points[i], strip_points[j]) min_dist = min(min_dist, dist) return min_dist ``` 其中,brute_force是暴力最近对距离的函数,distance是计算两个之间距离的函数。时间复杂度约为O(N log N)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_SCY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值