大家好,我是连人。本期讲解二维最近点对的问题。
一维点对问题十分简单,在此不再赘述,大致就如同二分法般不断将区域分为两段。
假设区域被分为A以左和B以右,A以左的最近距离d1,B以右的最近距离d2,以及A与B的距离d3取最小值,即为这片区域的最近距离。
首先,将点集按x从小到大排好,这一步的目的是使分区合理。
这个图中一共有十个点,根据分治策略,分为两个区,每个区五个点。
这是我看的无论是课本还是别的都画的是这条线,实际上在代码实现的时候,是这样分的。
在二维最近点对问题中,“A与B之间的距离d3”发生了些许变化。在一维点对问题中,A区域与B区域的最近距离一定是A与B之间的距离,而在二维问题中,情况会稍微不同,例如下图。
显然,这张图中,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]))
转载注明出处。