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

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

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

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

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

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

P.S:这不是作业题。

【问题讨论】:

  • 是的,可惜。这是一个有趣的问题,但已经在这里回答了。
  • @missingno:有点类似,但是我上面提供的解决方案可以很容易地解决这个问题。在这里,需要一些额外的计算,我想知道是否有办法将它们最小化。

标签: algorithm


【解决方案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 为您的问题编写了这个代码,希望对您有所帮助
      猜你喜欢
      • 2019-06-29
      • 1970-01-01
      • 2014-09-07
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-25
      相关资源
      最近更新 更多