【问题标题】:Find the index of points closest to each other in a list在列表中查找彼此最近的点的索引
【发布时间】:2020-08-27 02:28:23
【问题描述】:

我在实现该功能以找到最近点时遇到了麻烦,我尝试了多种方法,但我似乎无法弄清楚。关于我应该如何解决它的任何想法?

欧几里得距离

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)

【问题讨论】:

  • 有什么问题?
  • 用户 scipy.spatial.KDTree 而不是自己实现。
  • if dis &lt; min((dis)) 行中,这个min((dis)) 没有意义。您没有跟踪当前的最小距离。
  • 感谢大家提供自己的功能

标签: python python-3.x distance indexof euclidean-distance


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    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])
    

    【讨论】:

    • 哦,我从不知道组合,感谢您引起我的注意,一定会调查的。
    【解决方案3】:

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

    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)
    

    【讨论】:

      【解决方案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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-28
        • 1970-01-01
        • 2022-11-01
        • 2015-04-18
        • 1970-01-01
        • 2018-11-25
        • 1970-01-01
        • 2018-06-05
        相关资源
        最近更新 更多