分治法:二维最近点对问题

大家好,我是连人。本期讲解二维最近点对的问题。

一维点对问题十分简单,在此不再赘述,大致就如同二分法般不断将区域分为两段。

假设区域被分为A以左和B以右,A以左的最近距离d1B以右的最近距离d2,以及A与B的距离d3取最小值,即为这片区域的最近距离。

二维最近点对问题

首先,将点集按x从小到大排好,这一步的目的是使分区合理。

分治法:二维最近点对问题_第1张图片
这个图中一共有十个点,根据分治策略,分为两个区,每个区五个点。
分治法:二维最近点对问题_第2张图片
这是我看的无论是课本还是别的都画的是这条线,实际上在代码实现的时候,是这样分的。

分治法:二维最近点对问题_第3张图片

在二维最近点对问题中,“A与B之间的距离d3”发生了些许变化。在一维点对问题中,A区域与B区域的最近距离一定是A与B之间的距离,而在二维问题中,情况会稍微不同,例如下图。
分治法:二维最近点对问题_第4张图片
显然,这张图中,A区域与B区域最近的距离不是a与b而是a与c。

为了避免这种情况,我们将满足条件的点再次进行取最近点对算法,作为“A与B的距离d3”。

而这个条件则是:取d1与d2的最小距离d。A,B区域中所有水平距离与中点相距d以内的点。

原理就是如此,下面介绍代码部分。

首先使用归并排序将点集按顺序排序,之后放入分治算法中。

我的函数中,返回左侧点位,右侧点位和距离三个值。

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


def generate_point():
    points = []
    points_num = random.randint(1, 20)
    for i in range(points_num):
        x = random.randint(-50, 50)
        y = random.randint(-50, 50)
        points.append([x, y])
    return points


def merge(points, start, mid, end):
    temp = []
    i = start
    j = mid + 1
    while i < mid + 1 and j < end + 1:
        if points[i][0] > points[j][0]:
            temp.append(points[j])
            j += 1
        else:
            temp.append(points[i])
            i += 1
    while i < mid + 1:
        temp.append(points[i])
        i += 1
    while j < end + 1:
        temp.append(points[j])
        j += 1
    points[start:end + 1] = temp                    # 切片是左闭右开,实际不包含第end+1点


def merge_sort(points, start, end):
    if start < end:
        mid = start + (end - start) // 2
        merge_sort(points, start, mid)
        merge_sort(points, mid + 1, end)
        merge(points, start, mid, end)


def distance(p1, p2):
    x = math.fabs(p1[0] - p2[0])
    y = math.fabs(p1[1] - p2[1])
    return math.sqrt(x*x + y*y)


def get_closest_distance(points, l, r):
    if r <= l:                          # 报错或只有一个点,返回无限大
        return [0, 0, 65535]
    if r - l == 1:                          # 只有两个点直接求解
        return [l, r, distance(points[l], points[r])]
    if r - l == 2:                          # 三个点时使用比较的方法
        d1 = distance(points[l], points[l+1])
        d2 = distance(points[l+1], points[r])
        d3 = distance(points[l], points[r])
        if d1 <= d2 and d1 <= d3:
            return [l, l+1, d1]
        elif d2 <= d1 and d2 <= d3:
            return [l+1, r, d2]
        else:
            return [l, r, d3]
# 四个点以上使用分治法
    m = l + (r - l) // 2
    result1 = get_closest_distance(points, l, m)
    result2 = get_closest_distance(points, m+1, r)
    d = min(result1[2], result2[2])
    # 寻找m左侧和右侧范围为d内的所有点
    i = m
    j = m
    while i >= l:                               # 寻找左侧的值
        if points[m][0] - points[i][0] < d:
            i -= 1
        else:
            break
    while j <= r:                               # 寻找右侧的值
        if points[j][0] - points[m][0] < d:
            j += 1
        else:
            break
    if i < m:              # 如果m左侧没有符合条件的点了,此时i会停留在m,如果有,此时i会在最左侧的点之前一位,所以+1
        i += 1
    result3 = [0, 0, 65535]
    for a in range(i, m+1):                     # range()左闭右开,我们需要m点作为左侧的点
        for b in range(m+1, j):                 # j此时是右侧中最右边符合条件的点的位置+1,因为range,所以不包含j
            d0 = distance(points[a], points[b])
            if d0 < result3[2]:
                result3 = [a, b, d0]
    if result1[2] <= result2[2] and result1[2] <= result3[2]:
        return result1
    elif result2[2] <= result1[2] and result2[2] <= result3[2]:
        return result2
    else:
        return result3


p = generate_point()
print("随机生成点:")
print(p)
merge_sort(p, 0, len(p)-1)
print("归并排序:")
print(p)
result = get_closest_distance(p, 0, len(p)-1)
print("最近的两个点:" + str(p[result[0]]) + ", " + str(p[result[1]]))
print("距离为" + str(result[2]))

转载注明出处。

你可能感兴趣的:(递归和分治算法,python,算法,数据结构)