在列表中查找彼此最接近的点的索引

Find the index of points closest to each other in a list

为了找到最近点,我一直无法实现该功能,我已经尝试了多种方法,但我似乎无法弄清楚。关于我应该如何解决它有什么想法吗?

欧氏距离

def dist(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    dis = sqrt((x1-x2)**2 + (y1-y2)**2)
    return dis

函数

def offices_to_merge(points):
    min_p1 = 0
    min_p2 = 1
    for i in range(len(points)):
        for j in range(i+1, len(points)):
            dis = dist(points[i], points[j])
            if dis < min((dis)) :
               min_p1 = i
               min_p2 = j
    return (min_p1, min_p2)
>>> points = [(350, 150), (500, 250), (150, 150), (50, 400), (200, 100)]
>>> offices_to_merge(points)
(2, 4)

您可以使用 cdist 获取所有 points:

之间的所有距离
from scipy.spatial.distance import cdist
import numpy as np

points = [(350, 150), (500, 250), (150, 150), (50, 400), (200, 100)]

# calculate all distances between two sets of points
dists = cdist(points, points)
# the self distance is 0 -> we don't want this so make it large
dists[dists == 0] = dists.max()

# get index of smallest distance
np.unravel_index(dists.argmin(), dists.shape)
>>> (2, 4)

您的代码存在的问题是,您没有跟踪遍历点时观察到的当前最小距离:

def dist(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    dis = ((x1-x2)**2 + (y1-y2)**2)**0.5
    return dis

def offices_to_merge(points):
    current_minimum = float('inf')
    min_p1 = -1
    min_p2 = -1
    for i in range(len(points)):
        for j in range(i+1, len(points)):
            dis = dist(points[i], points[j])
            if dis < current_minimum:
               min_p1 = i
               min_p2 = j
               current_minimum = dis
    return (min_p1, min_p2)

points = [(350, 150), (500, 250), (150, 150), (50, 400), (200, 100)]
print( offices_to_merge(points) )

打印:

(2, 4)

您可以使用组合来查看所有可能的位置对。然后计算所有距离,取最小值,并确定产生该最小值的对的索引。

from numpy import sqrt
from itertools import combinations

def dist(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    dis = sqrt((x1-x2)**2 + (y1-y2)**2)
    return dis

points = [(350, 150), (500, 250), (150, 150), (50, 400), (200, 100)]

a = list(combinations(points, 2)) # combinations
b = [dist(el1,el2) for el1,el2 in a] # distances
idx = b.index(min(b)) # index of the min
print(a[idx])

如果您的点列表很大,将具有 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

此函数将 return 比列表中任何其他点对更接近的两个点。 请注意,如果 dist() 函数是 3D 距离计算,则 nearest2() 函数适用于 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)