【问题标题】:fastest method of getting k smallest numbers in unsorted list of size N in python?在python中获取大小为N的未排序列表中的k个最小数字的最快方法?
【发布时间】:2016-02-10 22:17:26
【问题描述】:

使用 python 在大小为 N 的未排序列表中获取 k 个最小数字的最快方法是什么?
对大数字列表进行排序,然后获得 k 个最小数字,或者通过在列表中查找最小值 k 次来获得 k 个最小数字,确保您从之前的搜索中删除找到的最小值,是否更快?下次搜索?

【问题讨论】:

  • 我强烈建议不要找到最小 k 次。
  • 是的,我认为这是 k^2 次查找
  • 在 Python 中这样做很好,但它实际上是一个与语言无关的问题。
  • @DavidEhrmann:当然,但是 Python 附带电池:import heapq; result = heapq.nsmallest(k, inputlist)。
  • 最快取决于k,N,取决于整数的大小(如果它们是整数),取决于可用的硬件(例如,使用并行堆排序),关于 Python 实现。

标签: python algorithm time-complexity


【解决方案1】:

你可以使用堆队列;它可以在 O(NlogK) 时间内从大小为 N 的列表中为您提供 K 个最大或最小的数字。

Python 标准库包括 heapq module,并带有一个已实现的 heapq.nsmallest() function:

import heapq

k_smallest = heapq.nsmallest(k, input_list)

在内部,这将创建一个大小为 K 的堆,其中包含输入列表的前 K 个元素,然后迭代剩余的 N-K 个元素,将每个元素推入堆,然后弹出最大的元素。这样的 push 和 pop 需要 log K 时间,使得整体操作 O(NlogK)。

该函数还优化了以下边缘情况:

  • 如果 K 为 1,则使用 min() 函数,得到 O(N) 结果。
  • 如果 K >= N,则函数使用排序代替,因为在这种情况下 O(NlogN) 会超过 O(NlogK)。

更好的选择是使用introselect algorithm,它提供了 O(n) 选项。我知道的唯一实现是使用numpy.partition() function:

import numpy

# assuming you have a python list, you need to convert to a numpy array first
array = numpy.array(input_list)
# partition, slice back to the k smallest elements, convert back to a Python list
k_smallest = numpy.partition(array, k)[:k].tolist()

除了需要安装numpy之外,这还需要N个内存(heapq为K),因为为分区创建了列表的副本。

如果您只想要索引,您可以使用任一变体:

heapq.nsmallest(k, range(len(input_list)), key=input_list.__getitem__)  # O(NlogK)
numpy.argpartition(numpy.array(input_list), k)[:k].tolist()  # O(N)

【讨论】:

  • 如果你向下滚动,会有一个关于在 O(n) 时间内完成的快速讨论。
  • @DavidEhrmann:不,因为选择第 k 个最小的项目在这里被掩盖得太快了。这就是堆队列的作用!
  • 无序列表中第k个元素的选择可以在n时间和常量内存中完成。堆队列两者都不做。
  • @jsky:你可以,使用heapq.nsmallest(k, range(len(input_list)), key=inputlist.__getitem__)。
  • @jsky: 或使用 numpy.argpartition() 用于 O(n) introselect 选项:numpy.argpartition(numpy.array(input_list), k)[:k].tolist()。
【解决方案2】:

如果不需要对第 k 个最小数字的列表进行排序,则可以使用像 introselect 这样的选择算法在 O(n) 时间内完成。标准库没有提供,但 NumPy 有 numpy.partition 来完成这项工作:

partitioned = numpy.partition(l, k)
# The subarray partitioned[:k] now contains the k smallest elements.

【讨论】:

    【解决方案3】:

    你可能想看看heapq:

    In [109]: L = [random.randint(1,1000) for _ in range(100)]
    
    In [110]: heapq.nsmallest(10, L)
    Out[110]: [1, 17, 17, 19, 24, 37, 37, 45, 63, 73]
    

    【讨论】:

      【解决方案4】:

      编辑:这假定列表是不可变的。如果列表是一个数组并且可以修改,则可以使用线性方法。

      您可以通过使用大小为k + 1 的堆将复杂度降低到O(n * log k)。

      1. 最初将第一个 k 元素放入最小堆中。
      2. 对于每个后续元素,将该元素添加为叶子并堆化。
      3. 用下一个元素替换最后一个元素。

      Heapify 可以在对数时间内完成,因此时间复杂度如上。

      【讨论】:

        【解决方案5】:

        您可以在O(kn) 中使用selection algorithm 进行操作。一旦kn >= n log n,切换到排序。也就是说,选择算法的常数往往比快速排序的常数高很多,所以您真的需要比较i (kn) 和j (n log n)。在实践中,通常更希望只进行排序,除非您处理的是大 n 或非常小的 k。

        编辑:见 cmets。它实际上是 很多 更好。

        【讨论】:

        • 我认为 map-reduce 不会比 max-heap 解决方案更快。
        • 排序需要 O(NlogN),堆队列给你 O(NlogK),并且列表只需要 N + K 内存(一个用于输入列表,一个用于堆),加上列表中的原始 N 个元素(Python 只是复制对堆的引用)。
        • 除非您正在运行 k 选择,否则选择算法不会采用 O(kn)。选择第 k 个最小的元素,然后返回比它更小的所有元素的列表要快得多。
        • 是的,它只需要 O(n)。
        • @MartijnPieters:使用introselect,也许使用NumPy implementation。 Introselect 是 O(n)。
        【解决方案6】:

        在 heapq 中使用 nsmallest numbers 代码较少,但如果您想自己实现它,这是一种简单的方法。该解决方案只需要循环一次数据,但由于 heappush 和 heappop 在 O(log n) 上运行,因此该算法在较小数量的 k 上表现最佳。

        import heapq
        
        def getsmallest(arr, k):
            m = [-x for x in l[:k]]
            heapq.heapify(m)
            for num in arr[5:]:
                print num, m
                heapq.heappush(m, max(-num, heapq.heappop(m)))
            return m
        
        if __name__ == '__main__':
            l = [1,2,3,52,2,3,1]
            print getsmallest(l, 5)
        

        【讨论】:

          猜你喜欢
          • 2021-01-06
          • 1970-01-01
          • 2010-12-08
          • 1970-01-01
          • 2017-08-29
          • 1970-01-01
          • 2014-04-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多