分治算法④-使用分治算法实现最近点对问题-python

该问题指的是:在一个平面上,有着许多的散点,如何找出欧式距离最近的两个点。从问题表面可以看出,该问题可以用暴力法求解,即求出所有点之间的距离,再统一进行比较,选出距离最近的点对。但这样做,难免时间复杂度较高,所以就需要用别的解法来计算,常用的是分治法:即按X坐标或Y坐标将元素区分成两部分,然后不断划分,直到每个子区间中只有一个元素,求出左右区间中最小点对之间的距离,这样就求出了两个区间最小值。接下来要求出最复杂的第三个最小值:两个点在不同的子区间,如何在不同区间寻找这两个点就成了最近点对问题的难点。

此问题的解决步骤,参考了网上一篇比较容易理解的文章:算法设计与分析——分治法:详解二维最近点对问题,具体步骤如下:    

1>选择所有点的某一坐标(X坐标或Y坐标,此处以X轴为例),求出平均值,划分中轴线,将数据按某一坐标轴分为左右两个部分。    

2>求出左半边和右半边的最小距离,选取较小值d,此步骤可以用递归实现。    

3>根据上一步求出的d,求出最小点对在不同区间时的最小距离。    

4>比较这三个最小距离。   

上述步骤中,较难理解的部分是第3步。首先在左右区间各选择一个点时,肯定是在某个区域中选择时,这个区域的宽度如何定义。正确的做法应该是<d,因为横坐标在d距离之外的点和另一个区间之间的距离肯定>d,如下:

d1和d2分别为左右区间中最小点对之间的距离,d为d1和d2的较小值。   接下来就要确定区域的高度范围了,这个范围不是固定的,需要根据每次选择的点来动态设置。假设我们在左半边选择了一个点,那这个点对应的Y轴坐标的2d之间的高度就是高度范围,因为2d之外的点与当前选择点之间的距离也肯定>d,如下:

算法实现-python:

'''
最小点对算法:给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。
主要算法思想:先把所有点按照x值排序,从最中间(x值中位数b)将所有点分为三个部分,左子域(左子域包括中间子域)、右子域、x值恰好等于中位数的中间子域,分别找出左右子域中的最近点,取最小值为d,
再将左右子域的x值范围缩减到[b-d,b+d]之间,循环遍历左子域中的每个点,在右子域中寻找y值离当前点最近的四个点,分别求距离,取最小距离,然后寻找中间子域中的最近点,取最小距离
通过递归(分治)的方法,最终正确查找到最近点对
'''
import math
​
# 输入
p = [[0, 0], [1, 1], [3, 4], [0, 3], [3.2, 4.2], [0, -1], [-2, -2], [-1, -2], [0, 0.4], [-1, 2], [0, 2], [0.5, 2]]
# p = [[0,0], [1,1], [3,4]]
n = len(p)
​
​
# 预期输出:最近点对是 ( 3.2 , 4.2 ) 和 ( 3 , 4 ), 最短距离为 0.2828427124746193
​
# 查找最接近的目标的y下标
def find_closed_index(collections, target):
    n = len(collections)
    low = 0
    high = n - 1
    mid = 0
    while low <= high:
        mid = math.floor((low + high) / 2)
        if target > collections[mid][1]:
            low = mid + 1
        elif target < collections[mid][1]:
            high = mid - 1
        else:
            return mid
​
    return mid
​
​
def distance(p1, p2):
    return math.sqrt(pow(p1[0] - p2[0], 2) + pow(p1[1] - p2[1], 2))
​
​
def divide(l, r, res):
    if l == r:
        return float("inf"), []
    if (l + 1) == r:
        return distance(p[l], p[r]), [p[l], p[r]]
    mid = math.floor((l + r) / 2)
    d1, res1 = divide(l, mid, res)
    d2, res2 = divide(mid + 1, r, res)
    if d1 >= d2:
        d = d2
        res = res2
    else:
        d = d1
        res = res1
​
    # 先将所有左子域、右子域、同一x值的中间子域,删选出来
    left = []
    right = []
    midd = []
    b = p[mid][0]
    bl = b - d
    br = b + d
    for i in range(n):
        if ((p[i][0] >= bl) & (p[i][0] <= b)) == True:
            left.append(p[i])
            if p[i][0] == b:
                midd.append(p[i])
        elif ((p[i][0] <= br) & (p[i][0] >= b)) == True:
            right.append(p[i])
​
    if len(right) == 0:
        return d, res
​
    # 将右子域先按照y值大小排好序
    right.sort(key=lambda x: x[1])
​
    # 遍历左子域中的每一个点,在右子域中寻找y值最邻近的四个点,求出最小距离以及最近点对
    for i in range(len(left)):
        closed_point = []
        right_num = len(right)
        if right_num <= 4:
            closed_point = right
        else:
            index = find_closed_index(right, left[i][1])
            if index >= 4:
                start = index - 4
            else:
                start = 0
            if index + 5 > len(right) - 1:
                end = len(right) - 1
            else:
                end = index + 5
​
            for j in range(start, end):
                closed_point.append(right[j])
​
            # 前四个就是右子域中离左子域最近的四个点
            closed_point.sort(key=lambda x: abs(x[1] - left[i][1]))
​
        if len(closed_point) >= 4:
            end2 = 4
        else:
            end2 = len(closed_point)
​
        for k in range(0, end2):
            dist = distance(closed_point[k], left[i])
            if dist < d:
                res = [closed_point[k], left[i]]
                d = dist
​
    # 再在中间子域的内部进行比较,看看有没有x值相同,且距离最近的两个点
    if len(midd) > 1:
        midd.sort()
        for j in range(len(midd) - 1):
            dist = distance(midd[j], midd[j + 1])
            if dist < d:
                res = [midd[j], midd[j + 1]]
                d = dist
​
    return d, res
​
​
# 前期需要按照x值排序
p.sort()
d, res = divide(0, n - 1, [])
print("最近点对是 (", res[0][0], ",", res[0][1], ") 和 (", res[1][0], ",", res[1][1], "), 最短距离为", d)
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

灰灰老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值