找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1835

积分

0

好友

226

主题
发表于 2025-12-25 19:54:56 | 查看: 33| 回复: 0

刚看到一个引发讨论的帖子,内容与程序员职业规划相关,但作为技术从业者,我们更应将注意力放在具体问题的解决上。昨天深夜,同事发来一个经典的算法问题求助:如何在平面上的一堆点中,找出距离最近的一对点。

这本质上是一个计算几何问题:给定平面上的点集 points,其中每个点为 (x, y) 坐标,目标是找出所有点对中欧几里得距离最小的那一对。

最直观的解法是暴力枚举。遍历每个点,计算它与其余所有点的距离,并更新最小值。

import math

def dist(p1, p2):
    # p1, p2: (x, y)
    dx = p1[0] - p2[0]
    dy = p1[1] - p2[1]
    return math.hypot(dx, dy)  # 等价于 sqrt(dx*dx + dy*dy)

def closest_pair_bruteforce(points):
    n = len(points)
    if n < 2:
        return float('inf'), None
    ans = float('inf')
    best_pair = None
    for i in range(n):
        for j in range(i + 1, n):
            d = dist(points[i], points[j])
            if d < ans:
                ans = d
                best_pair = (points[i], points[j])
    return ans, best_pair

该方法思路清晰,但时间复杂度为 O(n^2)。当点数达到10万级别时,计算量将难以承受。在面试或竞赛场景中,通常期望的是 O(n log n) 的更优解法。

分治法思路解析

更高效的算法基于分治思想。首先将所有点按 x 坐标排序,然后递归地将点集一分为二。

  • 左半部分点集内部的最近距离为 d_left
  • 右半部分点集内部的最近距离为 d_right

delta = min(d_left, d_right)。那么,整个点集的最近点对要么完全位于左侧或右侧(距离已为 delta),要么由一个左点和一个右点组成。

关键优化在于:对于这种跨分界线的点对,我们无需检查所有左右点组合。可以证明,只需考虑中线附近一个 *“宽度为 2 delta 的竖直带状区域” 内的点。更进一步,若将此带状区域内的点按 y 坐标排序,则每个点只需与后续 常数个(例如至多7个)** 点进行比较,即可确保找到最小距离。这一结论的证明涉及几何分析,此处我们专注于应用。

Python 分治实现

下面提供一个易于理解的分治实现版本,其复杂度优于暴力法,代码结构也较为清晰。

import math

def dist(p1, p2):
    dx = p1[0] - p2[0]
    dy = p1[1] - p2[1]
    return math.hypot(dx, dy)

def closest_pair(points):
    # 预处理:按x坐标排序
    pts = sorted(points, key=lambda p: p[0])

    def solve(l, r):
        # 处理区间 [l, r) 内的点
        n = r - l
        if n <= 3:
            # 点数很少时,直接暴力计算
            best = float('inf')
            best_pair = None
            for i in range(l, r):
                for j in range(i + 1, r):
                    d = dist(pts[i], pts[j])
                    if d < best:
                        best = d
                        best_pair = (pts[i], pts[j])
            return best, best_pair

        mid = (l + r) // 2
        mid_x = pts[mid][0]

        # 递归求解左右子问题
        d_left, pair_left = solve(l, mid)
        d_right, pair_right = solve(mid, r)

        # 确定当前最小距离 delta
        delta = d_left
        best_pair = pair_left
        if d_right < delta:
            delta = d_right
            best_pair = pair_right

        # 收集跨越中线的候选点(位于带状区域内)
        strip = []
        for i in range(l, r):
            if abs(pts[i][0] - mid_x) <= delta:
                strip.append(pts[i])

        # 将候选点按 y 坐标排序
        strip.sort(key=lambda p: p[1])

        # 在有序的 strip 中检查可能更小的距离
        m = len(strip)
        for i in range(m):
            j = i + 1
            # 只检查y坐标差小于delta的点
            while j < m and (strip[j][1] - strip[i][1]) < delta:
                d = dist(strip[i], strip[j])
                if d < delta:
                    delta = d
                    best_pair = (strip[i], strip[j])
                j += 1
        return delta, best_pair

    return solve(0, len(pts))

该函数返回最近距离 delta 以及对应的点对 best_pair

测试验证

可以通过简单测试对比暴力法与分治法的结果一致性。

if __name__ == "__main__":
    pts = [(0, 0), (1, 1), (2, 2), (5, 5), (1, 2), (3, 1)]
    d1, pair1 = closest_pair_bruteforce(pts)
    d2, pair2 = closest_pair(pts)
    print("暴力法结果:", d1, pair1)
    print("分治法结果:", d2, pair2)

对于大规模点集,分治法在性能上将有显著提升。理解其背后的分治思想是掌握此类算法问题的关键,而清晰的Python实现则有助于在实战中快速应用。




上一篇:gopup爬虫API库实战指南:高效获取微博、百度指数与宏观数据
下一篇:基于睿擎派(RK3506)的CANOpen DS401协议实战:工业IO模块数据采集与输出控制
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:19 , Processed in 0.223004 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表