找到离原点最近的 100 颗恒星的算法

Posted

技术标签:

【中文标题】找到离原点最近的 100 颗恒星的算法【英文标题】:Algorithm to find 100 closest stars to the origin 【发布时间】:2012-02-08 22:12:18 【问题描述】:

首先让我说出正确的问题:

问:有一个文件包含超过一百万个点 (x,y),每个点代表一颗星。在 (a,b) 有一个行星地球。现在,任务是构建一个算法,将 100 颗最近的恒星送回地球。你的算法的时间和空间复杂度是多少。

这个问题在各种采访中被问过很多次。我尝试查找答案,但找不到满意的答案。

一种方法,我认为可能是使用大小为 100 的最大堆。计算每颗星的距离并检查距离是否小于最大堆的根。如果是,将其替换为 root 并调用 heapify。

还有其他更好/更快的答案吗?

P.S:这不是作业题。

【问题讨论】:

Find the x smallest integers in a list of length n的可能重复 是的,可惜。这是一个有趣的问题,但已经在这里回答了。 @missingno:有点类似,但是我上面提供的解决方案可以很容易地解决这个问题。在这里,需要一些额外的计算,我想知道是否有办法将它们最小化。 【参考方案1】:

你实际上可以在时间 O(n) 和空间 O(k) 中做到这一点,其中 k 是你想要的最近点的数量,通过使用一个非常聪明的技巧。

selection problem 如下:给定一个元素数组和一些索引 i,重新排列数组的元素,使第 i 个元素在正确的位置,所有元素都小于第 i 个元素在左边,所有大于第 i 个元素的元素都在右边。例如,给定数组

40  10  00  30  20

如果我尝试根据索引 2(零索引)进行选择,一个结果可能是

10  00  20  40  30

由于索引 2 (20) 处的元素在正确的位置,因此左侧的元素小于 20,右侧的元素大于 20。

事实证明,由于这比实际对数组进行排序的要求不那么严格,因此可以在 O(n) 时间内完成此操作,其中 n 是数组的元素数。这样做需要一些复杂的算法,比如median-of-medians 算法,但确实是 O(n) 时间。

那么你如何在这里使用它?一种选择是将文件中的所有 n 个元素加载到一个数组中,然后使用选择算法在 O(n) 时间和 O(n) 空间中选择前 k 个(这里,k = 100)。

但实际上你可以做得比这更好!对于您想要的任何常数 k,请保持 2k 个元素的缓冲区。将文件中的2k个元素加载到数组中,然后使用选择算法重新排列,使最小的k个元素在数组的左半边,最大的在右半边,然后丢弃最大的k个元素(它们可以' t 是 k 个最近点中的任何一个)。现在,将文件中的 k 个更多元素加载到缓冲区中并再次执行此选择,并重复此操作,直到处理完文件的每一行。每次您进行选择时,您都会丢弃缓冲区中最大的 k 个元素并保留到目前为止您看到的 k 个最近点。因此,在最后,您可以最后一次选择前 k 个元素并找到前 k 个。

新方法的复杂性是什么?好吧,您将 O(k) 内存用于缓冲区和选择算法。您最终在大小为 O(k) 的缓冲区上调用 select 总共 O(n / k) 次,因为您在读取 ​​k 个新元素后调用 select。由于在大小为 O(k) 的缓冲区上进行选择需要时间 O(k),因此这里的总运行时间为 O(n + k)。如果 k = O(n)(一个合理的假设),这需要时间 O(n),空间 O(k)。

希望这会有所帮助!

【讨论】:

为此,我将再添加一项优化。在将新元素添加到缓冲区之前,如果它大于先前迭代中找到的第 k 个大元素,则丢弃它。而在这个“大于”测试中,你可以在测试实际距离之前先检查是否有单个坐标更大。这根本不会改变大O,但它避免了很多距离计算,并且平方根运算相当慢。所以你会得到一个更好的常数。 @btilly:你总是可以避免 sqrt 操作,因为 sqrt 是一个单调函数。最小化距离的点也会最小化距离平方(正方形抵消了 sqrt)。 @rrenaud 你是对的。但是乘法仍然比比较更昂贵,因此避免平方仍然是值得的。 优秀的算法和解释。 您是如何决定使用“2-times-k”元素的。为什么不使用“3-times-k”或其他类似的东西?【参考方案2】:

要详细说明 MaxHeap 解决方案,您将使用文件中的前 k 个元素(在本例中为 k = 100)构建一个最大堆。

最大堆的关键是它与地球的距离(a,b)。二维平面上两点之间的距离可以使用以下公式计算:

dist = (x1,y1) to (x2,y2) = square_root((x2 - x1)^2 + (y2 - y1)^2); 

这将花费 O(k) 时间来构建。对于从 k 到 n 的每个后续元素。即(n - k)个元素,您需要获取它与地球的距离并将其与最大堆的顶部进行比较。如果要插入的新元素比最大堆的顶部更靠近地球,则替换最大堆的顶部并在堆的新根上调用 heapify。

这需要 O((n-k)logk) 时间才能完成。 最后,我们将只剩下最大堆中的 k 个元素。您可以调用 heapify k 次来返回所有这些 k 元素。这是另一个 O(klogk)。

总体时间复杂度为 O(k + (n-k)logk + klogk)。

【讨论】:

【参考方案3】:

这是一个著名的问题,对此有很多解决方案: http://en.wikipedia.org/wiki/K-nearest_neighbor_algorithm

如果您觉得它没有用,还有一些其他资源,例如 Rurk 的计算几何书。

【讨论】:

本例中查询点是已知的,所以我们甚至不必去knn。【参考方案4】:

你的算法是正确的。请记住,您的程序的时间复杂度是 O(n . log 100 ) = O(n),除非要查找的最近点的数量可以变化。

【讨论】:

【参考方案5】:
import sys,os,csv

iFile=open('./file_copd.out','rU')
earth = [0,0]



##getDistance return distance given two stars
def getDistance(star1,star2):
    return sqrt((star1[0]-star2[0])**2 +(star1[1]-star2[1])**2 )


##diction dict_galaxy looks like this  key,distance  key is the seq assign to each star, value is a list [distance,its cordinance]
##1,[distance1,[x,y]];2,[distance2,[x,y]]
dict_galaxy=
#list_galaxy=[]
count = 0
sour=iFile.readlines()
for line in sour:
    star=line.split(',')   ##Star is a list [x,y]
    dict_galaxy[count]=[getDistance(earth,star),star]
    count++

###Now sort this dictionary based on their distance, and return you a list of keys.
list_sorted_key = sorted(dict_galaxy,key=lambda x:dict_galaxy[x][0])

print 'is this what you want %s'%(list_sorted_key[:100].to_s)
iFile.close()

【讨论】:

我刚刚用 Python 为您的问题编写了这个代码,希望对您有所帮助

以上是关于找到离原点最近的 100 颗恒星的算法的主要内容,如果未能解决你的问题,请参考以下文章

找到离给定点最近的点

数据结构与算法之深入解析“迷宫中离入口最近的出口”的求解思路与算法示例

图论 Dijkstra+堆优化

内功基础算法——栈和队列

K-Means 聚类中离质心最近的 M 个点

在android中找到离地理点最近的警察局