算法丨Python实现分治法解决最近对问题

最近对问题

目标是在一个给定的点集中找到距离最近的一对点。

解决最近对问题有两个常用的方法,一是蛮力法,二是本文记录的分治法

分治法Python实现:

# -*- coding:utf-8 -*-
import math

def distance(p1, p2):
    """计算两个点之间的距离"""
    return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)

def closest_pair(points):
    """最近对问题的实现"""
    # 对点集按照x坐标排序
    points.sort(key=lambda x: x[0])
    # 递归终止条件:如果点集中只有一个或两个点,则直接返回
    n = len(points)
    if n == 1:
        return None, None, math.inf
    elif n == 2:
        return points[0], points[1], distance(points[0], points[1])
    # 分治求解
    mid = n // 2
    left_points = points[:mid]
    right_points = points[mid:]
    left_pair = closest_pair(left_points)
    right_pair = closest_pair(right_points)
    # 取左右子集中的最小距离
    if left_pair[2] < right_pair[2]:
        d = left_pair[2]
        closest = left_pair
    else:
        d = right_pair[2]
        closest = right_pair
    # 考虑跨越左右子集的最近对
    mid_x = points[mid][0]
    # 在左边子集中找到横坐标最大的点
    left_max_x = max((p for p in left_points if mid_x - p[0] < d), key=lambda x: x[0], default=None)
    # 在右边子集中找到横坐标最小的点
    right_min_x = min((p for p in right_points if p[0] - mid_x < d), key=lambda x: x[0], default=None)
    if left_max_x is not None and right_min_x is not None:
        # 如果左右子集中都存在点
        # 计算横跨左右子集的距离,并更新最近对
        mid_strip = [(x, y) for x, y in points if mid_x - d <= x <= mid_x + d]
        closest_strip = closest_strip_pair(mid_strip, d)
        if closest_strip[2] < closest[2]:
            closest = closest_strip
    return closest

def closest_strip_pair(points, d):
    """计算跨越左右子集的最近对"""
    n = len(points)
    min_distance = d
    closest = None, None, min_distance
    # 根据y坐标排序
    points.sort(key=lambda x: x[1])
    # 暴力枚举
    for i in range(n):
        for j in range(i+1, n):
            if points[j][1] - points[i][1] > min_distance:
                break
            dist = distance(points[i], points[j])
            if dist < min_distance:
                closest = points[i], points[j], dist
                min_distance = dist
    return closest


# 示例:

# points = [(1, 2), (3, 5), (7, 1), (6, 8), (4, 3)]
points = [(2, 3), (12, 30), (40, 50), (5, 1), (12, 10), (3, 4)]
print(closest_pair(points))

运行结果:

((3, 5), (4, 3), 2.23606797749979)
((2, 3), (3, 4), 1.4142135623730951)

代码解释

  1. 在上面的代码中,首先定义了一个计算两个点之间距离的函数distance,就是初中学过的两个坐标之间的距离。
  2. 实现了一个最近对问题的函数closest_pair。在函数中,我们首先对点集按照x坐标排序,并根据点集大小分成左右两个子集。
  3. 然后,我们递归地在左右两个子集中分别求解最近对问题。接下来,我们计算左右子集中的最小距离,并记录最近对。
  4. 我们考虑跨越左右子集的最近对。如果跨越的最近对距离小于之前的最近对距离,则更新最近对。在计算跨越的最近对时,我们使用了一个辅助函数closest_strip_pair,它用于计算跨越左右子集的最近对。
  5. 最后,函数closest_pair返回最近对的坐标和距离。

总结思考

因为我本科就对算法课的学习不够深入,研究生阶段学起来有点吃力。

在这个分治法解决最近对问题时,将点按x升序排列,并在x//2处划分中线,此时分为两个子集,递归计算两个子集的各点对距离,并更新得到左右子集的最小距离Sl和Sr,到这里还比较好理解。

难点在于,最近点对一个在左子集一个在右子集,这时需要根据d = min{Sl,Sr},在中线m左右各取长度为d的区域,再递归计算这个区域里边的所有点对距离并取最小和d进行比较。之所以在中线m左右各取长度d的区域里找,是因为,如果不在这个区域,那么就属于左子集或者右子集中的点对,而这部分已经被计算过了,最小距离为d。

借助课本中的图:
在这里插入图片描述
(a)图就是表示在(m-d,m+d)这个区域中递归求这些点对的最近距离并和左右子集中更小的最近距离作比较。
(b)图这个用了鸽笼原理得出最多不超过6个点,我也没太看明白,如果不在乎时间复杂度的话,直接对中间区域递归解决就可以,或许以后我会思考明白吧。

附上课本中的伪代码,日后再学习:

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

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Magneto_万磁王

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

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

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

打赏作者

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

抵扣说明:

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

余额充值