找到一个列表中的点到另一列表中的点的最小距离之和?

时间:2019-05-29 07:35:20

标签: python algorithm optimization linear-algebra linear-programming

我有两个分别包含x和y个n维点的列表。我必须计算列表一中每个点(包含x个点)与第二个列表中每个点(包含y个点)的最小距离之和。我正在计算的距离是欧几里得距离。需要优化的解决方案。

我已经在Python中实现了其幼稚的解决方案。但是它的时间复杂度太大,无法在任何地方使用。将有可能进行优化。这种问题的时间复杂度可以比我已经实现的降低吗?

我正在阅读我正在尝试实现的paper。在这种情况下,他们遇到了类似的问题,他们说这是Earth Mover Distance的特殊条件。由于没有给出代码,因此无法知道它是如何实现的。因此,我的幼稚实现,上面的代码太慢,无法处理11k文档的数据集。我使用Google Colab执行代码。

library(shiny)
library(DT)
library(dplyr)

shinyApp(
  ui = fluidPage(
    DT::dataTableOutput("table")
  ),
  server = function(input, output) {

  # create a summary table
    summary_iris <- group_by(iris, Species) %>%
      summarise(Count = n())

    summary_iris$Species <- levels(summary_iris$Species)

    shinyInput <- function(FUN, len, id, label, ...) {
      inputs <- character(len)

      for (i in seq_len(len)) {
        label <- summary_iris$Species[i]
        inputs[i] <- as.character(FUN(paste0(id, i),label=label, ...))
      }
      inputs
    }

    output$table <- DT::renderDataTable({
      DT <- summary_iris %>%
        mutate(Species = shinyInput(actionButton,
                                    nrow(summary_iris), 'button_', label = Species, class="bttn-unite",
                                    onclick = 'Shiny.onInputChange(\"select_button\",  this.id)'))
      DT::datatable(DT, rownames = FALSE, escape = FALSE,
                    options = list(rowCallback = JS(
                      "function(row, data) {",
                      "var full_text = data[0]",
                      "$('td:eq(0)', row).attr('title', full_text);",
                      "}")))
    })
  }
)

4 个答案:

答案 0 :(得分:0)

要计算两点之间的距离,可以使用距离公式:

enter image description here

您可以像在python中那样实现:

import math

def dist(x1, y1, x2, y2):
    return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))

然后,您需要做的是循环遍历X或Y列表,检查两点的距离,如果它在当前存储的最小距离之下,则将其存储。您最终应该使用O(n²)复杂度算法,这似乎是您想要的。这是一个工作示例:

min_dd = None
for i in range(len(l1)):
    for j in range(i + 1, len(l1)):
        dd = dist(l1[i], l2[i], l1[j], l2[j])
        if min_dd is None or dd < min_dd:
            min_dd = dd

有了这个,即使积分很多,您也可以获得不错的表现。

答案 1 :(得分:0)

为了减少运行时间,我建议找到曼哈顿距离(delta x + delta y),对每个点对结果数组进行排序,如果排序列表中的值在其中,则创建最小曼哈顿距离+ 20%的缓冲区在+ 20%的范围内,您可以计算出欧式距离并找到正确/最小的欧式答案。

这将减少一些时间,但是如果所有点都靠近在一起,则20%的数字可能不会减少时间,因为它们中的大多数将适合缓冲区,请尝试微调20%参数以查看最适合的参数您的数据集。请记住,由于欧几里得距离与曼哈顿距离的关系,将其减少太多可能会导致答案不准确。

答案 2 :(得分:0)

这与k近邻问题相似,因此找到与给定点的每个最接近点的成本为O(N),对于您的问题,应该为O(N ^ 2)。

有时候,如果您的数据是低维的,则使用kd-tree可能会提高性能。

答案 3 :(得分:0)

小数组

对于分别为xy的两个numpy数组(n,)(m,),您可以向量化距离计算,然后获得最小距离:

import numpy as np

n = 10
m = 20

x = np.random.random(n)
y = np.random.random(m)

# Using squared distance matrix and taking the
# square root at the minimum value
distance_matrix = (x[:,None]-y[None,:])**2
minimum_distance_sum = np.sum(np.sqrt(np.min(distance_matrix, axis=1)))

对于形状为(n,l)(m,l)的数组,您只需要将distance_matrix计算为:

distance_matrix = np.sum((x[:,None]-y[None,:])**2, axis=2)

或者,您可以使用np.linalg.normscipy.spatial.distance.cdistnp.einsum等,但是在许多情况下它们并不快。

大数组

如果上面的lnm太大,以至于无法将distance_matrix保留在内存中,则可以使用欧几里德距离的数学上下边界来增加速度(请参阅this paper。由于这依赖于for循环,因此速度会非常慢,但是可以使用numba包裹函数来解决此问题:

import numpy as np
import numba

@numba.jit(nopython=True, fastmath=True)
def get_squared_distance(a,b):
    return np.sum((a-b)**2)

def get_minimum_distance_sum(x,y):
    n = x.shape[0]
    m = y.shape[0]
    l = x.shape[1]

    # Calculate mean and standard deviation of both arrays
    mx = np.mean(x, axis=1)
    my = np.mean(y, axis=1)
    sx = np.std(x, axis=1)
    sy = np.std(y, axis=1)
    return _get_minimum_distance_sum(x,y,n,m,l,mx,my,sx,sy)

@numba.jit(nopython=True, fastmath=True)
def _get_minimum_distance_sum(x,y,n,m,l,mx,my,sx,sy):
    min_distance_sum = 0
    for i in range(n):
        min_distance = get_squared_distance(x[i], y[0])
        for j in range(1,m):
            if i == 0 and j == 0:
                continue
            lower_bound = l * ((mx[i] - my[j])**2 + (sx[i] - sy[j])**2)
            if lower_bound >= min_distance:
                continue
            distance = get_squared_distance(x[i], y[j])
            if distance < min_distance:
                min_distance = distance
        min_distance_sum += np.sqrt(min_distance)

    return min_distance_sum

def test_minimum_distance_sum():
    # Will likely be much larger for this to be faster than the other method
    n = 10
    m = 20
    l = 100

    x = np.random.random((n,l))
    y = np.random.random((m,l))

    return get_minimum_distance_sum(x,y)

此方法应该比增加阵列大小的前一种方法更快。如本文所述,该算法可以稍作改进,但是任何加速都将在很大程度上取决于数组的形状。

时间

在我的笔记本电脑上,在两个形状为(1000,100)的阵列上,您的方法需要大约1分钟,“小阵列”方法需要690毫秒,而“大阵列”方法则需要288毫秒。对于形状为(100, 3)的两个数组,您的方法需要28 ms,“小数组”方法需要429μs,而“大数组”方法则需要578μs。