我有两个分别包含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);",
"}")))
})
}
)
答案 0 :(得分:0)
要计算两点之间的距离,可以使用距离公式:
您可以像在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)
对于分别为x
和y
的两个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.norm
,scipy.spatial.distance.cdist
,np.einsum
等,但是在许多情况下它们并不快。
如果上面的l
,n
和m
太大,以至于无法将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。