如果您的点列表很大,那么将每个具有 O(N^2) 时间复杂度的点配对的蛮力方法将很快成为性能瓶颈。
有一种方法可以在 O(NlogN) 时间内得到结果,方法是根据点到任意基点的距离进行排序,该基点小于所有其他点(即下方和左侧)。使用这种排序方法,可以将点配对限制为仅在目前找到的最短距离范围内的点。
这是一个例子:
def dist(a,b): return ((a[0]-b[0])**2 + (a[1]-b[1])**2)**0.5
def nearest2(points):
minP1,minP2 = points[:2]
minDist = dist(minP1,minP2)
base = tuple(map(min,zip(*points)))
sPoints = sorted((dist(base,p),p) for p in points)
iMin = 0
for ix,(xDist,px) in enumerate(sPoints[1:],1):
for i,(iDist,pi) in enumerate(sPoints[iMin:ix],iMin):
if iDist + minDist <= xDist: iMin = i+1; continue
if dist(px,pi) >= minDist: continue
minP1,minP2 = px,pi
minDist = dist(minP1,minP2)
return minP1,minP2
此函数将返回比列表中任何其他点对彼此更近的两个点。 请注意,如果 dist() 函数是 3D 距离计算,则最近的 2() 函数将适用于 3 维中的点列表
print(nearest2(points))
((200, 100), (150, 150))
出于比较目的,以下是蛮力方法的外观(类似于您的函数):
def bruteForce(points):
minP1,minP2 = points[:2]
minDist = dist(minP1,minP2)
for i,p1 in enumerate(points[:-1]):
for p2 in points[i+1:]:
if dist(p1,p2) >= minDist: continue
minP1,minP2 = p1,p2
minDist = dist(minP1,minP2)
return minP1,minP2
测量性能差异(1000 分)说明了基于排序的方法的好处:
from random import randint
from timeit import timeit
count = 1
points = list(set( (randint(0,10000),randint(0,10000)*10) for _ in range(1000)))
t = timeit(lambda:nearest2(points),number=count)
print("nearest2 ",t) # 0.0022362289999999785
t = timeit(lambda:bruteForce(points),number=count)
print("bruteForce",t) # 0.36930638299999996
这快了 150 倍以上,并且随着您添加更多点,差异会更大
如果您需要列表中的索引而不是点本身,您可以调整nearest2() 函数或将其包装在从结果点对中查找索引的函数中:
def nearest2Index(points):
p1,p2 = nearest2(points) # bruteForce(points)
iP1 = points.index(p1)
iP2 = points.index(p2)
if iP1 == iP2: iP2 += points[iP1+1:].index(p2) + 1
if iP1>iP2: iP1,iP2 = iP2,iP1
return iP1,iP2
points = [(350, 150), (500, 250), (150, 150), (50, 400), (200, 100)]
print(nearest2Index(points)) # (2,4)