计算 (x,y,z) 点列表中最近邻点的(欧氏)距离的最有效方法是什么?

What is the Most Efficient Way to Compute the (euclidean) Distance of the Nearest Neighbor in a List of (x,y,z) points?

计算数组中每个点的最近邻居的(欧氏)距离的最有效方法是什么?

我有一个包含 100k (X,Y,Z) 个点的列表,我想计算一个最近邻距离列表。距离的索引将对应于点的索引。

我研究过 PYOD 和 sklearn 邻居,但那些似乎需要 "teaching"。我认为我的问题比这更简单。对于每个点:找到最近的邻居,计算距离。

示例数据:

points = [
     (0             0   1322.1695
      0.006711111   0   1322.1696
      0.026844444   0   1322.1697
      0.0604        0   1322.1649
      0.107377778   0   1322.1651
      0.167777778   0   1322.1634
      0.2416        0   1322.1629
      0.328844444   0   1322.1631
      0.429511111   0   1322.1627...)]

计算 k = 1 最近邻距离

结果格式:

results = [nearest neighbor distance]

示例结果:

results = [
0.005939372
0.005939372
0.017815632
0.030118587
0.041569616
0.053475883
0.065324964
0.077200014
0.089077602)
]

更新:

我已经实施了建议的两种方法。

  1. 使用scipy.spatial.cdist计算全距离矩阵
  2. 使用半径 R 内最近的 X 个邻居来查找每个点的邻居距离子集,return 最小的。

结果是方法 2 比方法 1 快,但实施起来要花更多的精力(有道理)。

似乎方法 1 的限制因素是 运行 完整计算所需的内存,尤其是当我的数据集接近 10^5 (x, y, z) 点时。对于我的 23k 点数据集,捕获最小距离需要大约 100 秒。

对于方法 2,速度缩放为 n_radius^2。也就是说,"neighbor radius squared",这实际上意味着该算法与包含的邻居数量成线性关系。使用 ~ 5 的半径(给定的应用程序绰绰有余),对于 23k 点的集合,需要 5 秒才能提供与 point_list 本身顺序相同的分钟列表。 "exact solution"和方法2的差异矩阵基本为零

感谢大家的帮助!

这个怎么样?

from scipy.spatial import distance

A = (0.003467119 ,0.01422762 ,0.0101960126)
B = (0.007279433  ,0.01651597  ,0.0045558849)
C = (0.005392258  ,0.02149997  ,0.0177409387)
D = (0.017898802  ,0.02790659  ,0.0006487222)
E = (0.013564214  ,0.01835688  ,0.0008102952)
F = (0.013375397  ,0.02210725 ,0.0286032185)

points = [A, B, C, D, E, F]
results = []
for point in points:
    distances = [{'point':point, 'neighbor':p, 'd':distance.euclidean(point, p)} for p in points if p != point]
    results.append(min(distances, key=lambda k:k['d']))

结果将是一个对象列表,如下所示:

results = [
    {'point':(x1, y1, z1), 'neighbor':(x2, y2, z2), 'd':"distance from point to neighbor"},
...]

其中 point 是参考点,neighbor 是点的最近邻居。

您可用的最快选项可能是 scipy.spatial.distance.cdist,它会找到输入中所有点之间的成对距离。虽然查找所有这些距离可能不是查找最近邻居的最快算法,但 cdist 是在 C 中实现的,因此它可能 运行 比您在 Python 中尝试的任何方法都快。

import scipy as sp
import scipy.spatial
from scipy.spatial.distance import cdist

points = sp.array(...)
distances = sp.spatial.distance.cdist(points)

# An element is not its own nearest neighbor
sp.fill_diagonal(distances, sp.inf)

# Find the index of each element's nearest neighbor
mins = distances.argmin(0)

# Extract the nearest neighbors from the data by row indexing
nearest_neighbors = points[mins, :]

#  Put the arrays in the specified shape
results = np.stack((points, nearest_neighbors), 1)

理论上你可以使这个 运行 更快(主要是通过将所有步骤组合到一个算法中),但除非你用 C 编写,否则你将无法与 SciPy/NumPy.

(cdist 运行s in Θ(n2) 时间(如果每个点的大小固定),每隔一部分O(n) 时间的算法,所以即使你确实尝试优化 Python 中的代码,你也不会注意到少量数据的变化,并且改进会被 cdist 获取更多数据。)

类似于 Caleb 的回答,但如果距离大于之前的某个最小距离(抱歉 - 无代码),您可以停止迭代循环。

我曾经为视频游戏编程。计算两点之间的实际距离会花费太多CPU。我们所做的是将 "screen" 划分为更大的笛卡尔正方形,如果 Delta-X 或 Delta-Y 为 "too far away",则避免实际距离计算 - 这只是减法,所以也许类似的东西可以限定需要实际的欧几里得距离度量计算(根据需要扩展到 n 维)?

编辑 - 扩展 "too far away" 候选对选择评论。 为简洁起见,我将假设一个二维景观。 取兴趣点 (X0,Y0) 和 "draw" 围绕该点的 nxn 正方形,原点为 (X0,Y0)。

遍历初始点列表并形成该方块内的候选点列表。这样做时,如果 DeltaX [ABS(Xi-X0)] 在正方形之外,则无需计算 DeltaY。

如果没有候选点,将方块变大并迭代

如果正好有一个候选点,并且在正方形内接圆的半径范围内,那就是你的最小值。

如果有"too many"个候选点,把方块变小,但是你只需要重新检查这个迭代的候选列表,而不是所有的点。

如果没有 "too many" 个候选者,则计算该列表的距离。这样做时,首先为第一个候选计算 DeltaX^2 + DeltaY^2。如果对于后续候选者,DetlaX^2 大于目前的最小值,则无需计算 DeltaY^2。

如果该计算的最小值在正方形内切圆的半径范围内,则该最小值就是最小值。

如果不是,您需要返回到之前的候选列表,该列表包括圆内具有该最小半径的点。例如,如果您以 2x2 正方形中的一个候选项结束,而该候选项恰好位于顶点 X=1、Y=1 上,则 distance/radius 将为 SQRT(2)。因此,返回到先前的候选列表,该列表的平方大于或等于 2xSQRT(2)。

如果有必要,生成一个新的候选列表,该列表仅包含 +/- SQRT(2) 方块内的点。 如上所述计算那些候选点的距离 - 忽略任何超过目前计算的最小值的距离。

在只有一个候选者之前,无需计算 Delta^2 之和的平方根。

如何调整初始正方形的大小,或者它是否应该是矩形,以及如何增加或减少 square/rectangle 的大小可能会受到数据分布的应用知识的影响。

如果您使用的语言支持,我会考虑其中的一些递归算法。