【问题标题】:Retrieve the two highest item from a list containing 100,000 integers从包含 100,000 个整数的列表中检索两个最高的项目
【发布时间】:2011-02-13 21:46:08
【问题描述】:

如何从包含 100,000 个整数的列表中检索最高的两个项目,而不必先对整个列表进行排序?

【问题讨论】:

    标签: python list sorting


    【解决方案1】:

    您遍历列表,维护包含迄今为止遇到的最高和第二高项目的值的变量。遇到的每个新项目都将替换新项目高于的两者中的任何一个(如果有的话)。

    【讨论】:

    • 这是要走的路。 O(n) 时间和 O(1) 空间。
    • @Jacob,将其推广到前 n 个变量的最佳方法是什么?我无法保持头脑清醒。你会使用列表吗?并对该列表进行排序?
    • @kamula 前 N 个项目的棘手之处在于避免将每个新项目与所有前 N 个项目进行比较。如果 N 很大,最好的办法可能是将前 N 个变量存储在某种二叉树中,这样对于每个新项目,您可以快速确定应该替换哪个项目(如果有)。可能还值得分别维护您目前看到的最高项目和第 N 个项目的变量,这样您就可以快速判断,对于每个新项目,您是否需要深入研究您的树(您只需要搜索如果新项目位于顶部项目和第 N 个项目之间,则树)。
    【解决方案2】:

    遍历整个列表是不排序的唯一方法。

    【讨论】:

      【解决方案3】:

      如果不对列表进行排序,唯一真正做到这一点的方法是遍历整个列表并保存最高的两个数字。我认为你最好对列表进行排序。

      【讨论】:

      • 我假设这个数字列表会在某个时候发生变化。如果您的列表已排序,则不必每次都进行迭代以找到最大数量。如果您更进一步,将数据结构存储在某种二叉树中,您将能够非常快速地提取最大数字。 (以及您想要的任何其他操作,例如检查重复项等)
      【解决方案4】:

      这会起作用,但我不知道您是否要保留列表中的项目:

      max1 = max(myList)
      myList.remove(max1)
      max2 = max(myList)
      

      如果你这样做,你可以这样做:

      max1 = max(myList)
      idx1 = myList.index(max1)
      myList.pop(idx1)
      
      max2 = max(myList)
      myList.insert(idx1,max1)
      

      【讨论】:

      • 这两个都迭代myList 3 次,实际上只需要一次迭代。
      • 我明白了。然而,这需要更少的算法,有时这就是人们想要的。如果是我,我会排序或遍历列表。这些方法已经在答案中多次介绍过。
      • @Jeff,排序会比这更糟糕。
      • 是的,我猜 O(3n) 比 O(n log n) 好。 :) 遍历数组,沿途保留最大值是 O(n),即 == O(3n)。在实时情况下,它们可能在最坏情况下相差 3 倍,但在可扩展性方面,它们是等效的。
      • 为什么我对此投了反对票?这是一个有效的答案。可能不是最佳答案,但它是有效的。
      【解决方案5】:

      第二高的项目是一个相当简单的案例,但是对于第 k 高的项目,您想要的是 selection algorithm。该页面非常详尽,因此最好只是阅读它。

      【讨论】:

        【解决方案6】:

        一个非常巧妙的方法是使用heapqHeapify the array (O(n)),然后只需弹出许多您需要的元素 (log(n))。 (在一次采访中看到这个问题,很好的问题要记住。)

        【讨论】:

        • +1。这是一个很好的方法。唯一的问题是堆用作优先级队列。它没有排序,唯一可靠的是队列的顶部将是最大的。因此,在弹出之后,您将不得不再次堆积队列(log(n),就像 zdav 提到的那样)。它不会改变渐近时间,但应该注意这一点。
        • 同意@Noufal,如果你只使用 heappop,重新堆化会被处理。
        • 我喜欢这个答案,但说这没有排序有点牵强。当然,这不是线性排序的,但有一定程度的排序。
        • 如果你知道 heapq,你应该知道:docs.python.org/library/heapq.html#heapq.nlargest
        • @Jeff B:查找列表的第一个(或等效的最后一个)M 元素必然涉及一些排序。这种方式比“简单”的手动编码循环更前卫,但不幸的是它也做了更多的,并且也占用了更多的空间......
        【解决方案7】:

        使用heapq.nlargest。如果您想处理的不仅仅是前两个元素,这是最灵活的方法。

        这是一个例子。

        >>> import heapq
        >>> import random
        >>> x = range(100000)
        >>> random.shuffle(x)
        >>> heapq.nlargest(2, x)
        [99999, 99998]
        

        【讨论】:

        • 对不起,我的小事,但这并不能真正回答这个问题。 OP 专门要求解决方案而不对列表进行排序 - 而 heapq.nlargest 的文档特别说它等同于排序。
        • @Korem 它提供了一个等效的result
        • @Korem 我相信它实际上是 O(nlogk),在这种情况下 k 是 2。(堆只达到这个大小。)
        • 我进行了一项测试,发现 nlargest 的速度是使用长度为 1000 的无序列表进行排序的两倍。(nlargest(2, x)sorted(x, reverse=True)[:2]
        • @FogleBird 这很有趣。我很高兴我发表了评论,我学到了一些新东西。
        【解决方案8】:

        您可以期待的最佳时间是线性的,因为您至少必须查看所有元素。

        这是我解决问题的伪代码:

        //assume list has at least 2 elements
        (max, nextMax) = if (list[0] > list[1])
                         then (list[0], list[1])
                         else (list[1], list[0])
        
        for (2 <= i < length) {
            (max, nextMax) = if       (max < list[i])     => (list[i], max)
                             elseif   (nextMax < list[i]) => (max, list[i])
                             else     (no change)         => (max, nextMax)
        }
        
        return (max, nextMax)
        

        【讨论】:

          【解决方案9】:

          JacobM's answer 绝对是要走的路。但是,在实施他所描述的内容时,需要牢记一些事项。这是一个在家玩的小教程,可指导您完成解决此问题的棘手部分。

          如果此代码用于生产,请使用列出的更有效/更简洁的答案之一。这个答案是针对刚接触编程的人。

          想法

          这个想法很简单。

          • 保留两个变量:largestsecond_largest
          • 浏览列表。
            • 如果项目大于largest,则将其分配给largest
            • 如果项目大于second_largest,但小于largest,则将其分配给second_largest

          开始

          让我们开始吧。

          def two_largest(inlist):
              """Return the two largest items in the sequence. The sequence must
              contain at least two items."""
              for item in inlist:
                  if item > largest:
                      largest = item
                  elif largest > item > second_largest:
                      second_largest = item
              # Return the results as a tuple
              return largest, second_largest
          
          # If we run this script, it will should find the two largest items and
          # print those
          if __name__ == "__main__":
              inlist = [3, 2, 1]
              print two_largest(inlist)
          

          好的,我们现在将 JacobM 的答案作为 Python 函数。当我们尝试运行它时会发生什么?

          Traceback (most recent call last):
            File "twol.py", line 10, in <module>
              print two_largest(inlist)
            File "twol.py", line 3, in two_largest
              if item > largest:
          UnboundLocalError: local variable 'largest' referenced before assignment
          

          显然,我们需要在开始循环之前设置largest。这可能意味着我们也应该设置second_largest

          初始化变量

          让我们将largestsecond_largest 设置为0。

          def two_largest(inlist):
              """Return the two largest items in the sequence. The sequence must
              contain at least two items."""
              largest = 0 # NEW!
              second_largest = 0 # NEW!
              for item in inlist:
                  if item > largest:
                      largest = item
                  elif largest > item > second_largest:
                      second_largest = item
              # Return the results as a tuple
              return largest, second_largest
          
          # If we run this script, it will should find the two largest items and
          # print those
          if __name__ == "__main__":
              inlist = [3, 2, 1]
              print two_largest(inlist)
          

          很好。让我们运行它。

          (3, 2)
          

          太棒了!现在让我们测试inlist[1, 2, 3]

              inlist = [1, 2, 3] # CHANGED!
          

          让我们试试吧。

          (3, 0)
          

          ...呃哦。

          修复逻辑

          最大值 (3) 似乎是正确的。但是,第二大值是完全错误的。怎么回事?

          让我们来看看这个函数在做什么。

          • 当我们开始时,largest 为 0,second_largest 也为 0。
          • 我们查看的列表中的第一项是 1,因此largest 变为 1。
          • 下一项是2,所以largest变成了2。

          但是second_largest呢?

          当我们为largest 分配一个新值时,最大值实际上变成了第二大值。我们需要在代码中显示出来。

          def two_largest(inlist):
              """Return the two largest items in the sequence. The sequence must
              contain at least two items."""
              largest = 0
              second_largest = 0
              for item in inlist:
                  if item > largest:
                      second_largest = largest # NEW!
                      largest = item
                  elif largest > item > second_largest:
                      second_largest = item
              # Return the results as a tuple
              return largest, second_largest
          
          # If we run this script, it will should find the two largest items and
          # print those
          if __name__ == "__main__":
              inlist = [1, 2, 3]
              print two_largest(inlist)
          

          让我们运行它。

          (3, 2)
          

          太棒了。

          初始化变量,第 2 部分

          现在让我们用一个负数列表来试试吧。

              inlist = [-1, -2, -3] # CHANGED!
          

          让我们运行它。

          (0, 0)
          

          这根本不对。这些零是从哪里来的?

          事实证明,largestsecond_largest 的起始值实际上大于列表中的所有项目。您可能考虑的第一件事是将largestsecond_largest 设置为Python 中可能的最低值。不幸的是,Python 没有最小的可能值。这意味着,即使您将它们都设置为 -1,000,000,000,000,000,000,您也可以得到一个小于该值的列表。

          那么最好的办法是什么?让我们尝试将largestsecond_largest 设置为列表中的第一项和第二项。然后,为了避免重复计算列表中的任何项目,我们只查看列表中第二个项目之后的部分。

          def two_largest(inlist):
              """Return the two largest items in the sequence. The sequence must
              contain at least two items."""
              largest = inlist[0] # CHANGED!
              second_largest = inlist[1] # CHANGED!
              # Only look at the part of inlist starting with item 2
              for item in inlist[2:]: # CHANGED!
                  if item > largest:
                      second_largest = largest
                      largest = item
                  elif largest > item > second_largest:
                      second_largest = item
              # Return the results as a tuple
              return largest, second_largest
          
          # If we run this script, it will should find the two largest items and
          # print those
          if __name__ == "__main__":
              inlist = [-1, -2, -3]
              print two_largest(inlist)
          

          让我们运行它。

          (-1, -2)
          

          太棒了!让我们尝试另一个负数列表。

              inlist = [-3, -2, -1] # CHANGED!
          

          让我们运行它。

          (-1, -3)
          

          等等,什么?

          初始化变量,第 3 部分

          让我们再次单步执行我们的逻辑。

          • largest 设置为 -3
          • second_largest 设置为 -2

          在那儿等着。已经,这似乎是错误的。 -2 大于 -3。这是导致问题的原因吗?让我们继续吧。

          • largest 设置为 -1; second_largest 设置为 largest 的旧值,即 -3

          是的,这看起来是个问题。我们需要确保largestsecond_largest设置正确。

          def two_largest(inlist):
              """Return the two largest items in the sequence. The sequence must
              contain at least two items."""
              if inlist[0] > inlist[1]: # NEW
                  largest = inlist[0]
                  second_largest = inlist[1]
              else: # NEW
                  largest = inlist[1] # NEW
                  second_largest = inlist[0] # NEW
              # Only look at the part of inlist starting with item 2
              for item in inlist[2:]:
                  if item > largest:
                      second_largest = largest
                      largest = item
                  elif largest > item > second_largest:
                      second_largest = item
              # Return the results as a tuple
              return largest, second_largest
          
          # If we run this script, it will should find the two largest items and
          # print those
          if __name__ == "__main__":
              inlist = [-3, -2, -1]
              print two_largest(inlist)
          

          让我们运行它。

          (-1, -2)
          

          非常好。

          结论

          这里是代码,经过很好的注释和格式化。它也有我能找到的所有错误。享受吧。

          但是,假设这确实是一个家庭作业问题,我希望您从看到一段不完美的代码慢慢改进中获得一些有用的经验。我希望其中一些技术在未来的编程任务中会有所帮助。


          效率

          效率不高。但对于大多数用途来说,应该没问题:在我的计算机(Core 2 Duo)上,可以在 0.27 秒内处理 100 000 个项目的列表(使用 timeit,平均运行 100 次以上)。

          【讨论】:

          • 我认为 Wesley 解决方案不好。相反,我认为这太可怕了。(也许对教如何修复代码很有用)简单总比复杂好。我们有很多解决方案,我们必须采取更简单的方法。在 100.000 个整数的列表中使用“for”或“while”是愚蠢的
          • 很遗憾你在heapq.nlargest的时候写了一个谩骂:)
          • heapq 确实可能是生产代码中使用的方式,但是向新程序员解释 for 循环比使用堆数据结构要容易得多。希望有一天一些新程序员会发现这很有用。也许吧。
          • 好的,现在如果我想要前三名怎么办?还是4?这种方法幼稚且容易出错(如您所示)。
          • 如果您的目标是教学,我们应该解释堆以及它们为何适合这项任务。
          【解决方案10】:

          “2最高”是不可能的;只有一项可以是“最高的”。也许您的意思是“最高的 2”。无论如何,当列表包含重复项时,您需要说明该怎么做。你想从 [8, 9, 10, 10]: (10, 9) 还是 (10, 10) 中得到什么?如果您的回答是 (10, 10),请考虑输入 [8, 9, 10, 10, 10]。当你得到“最高的两个”时,你打算怎么办?请编辑您的问题以提供此指导。

          同时,这里有一个采用第一种方法的答案(两个唯一值):

          largest = max(inlist)
          second_largest = max(item for item in inlist if item < largest)
          

          您应该针对列表中少于 2 个的唯一值添加防护措施。

          【讨论】:

          • 如果有重复,上面的 heapq.nlargest 建议将返回 [10,10]。如果你不关心重复,那么先通过 set() 传递列表,即 heapq.nlargest(2, set(my_list))
          【解决方案11】:

          将您的List 复制到List_copy。 检索最高值并通过以下方式获取其位置:

          Highest_value = max(List_copy)
          Highest_position = List_copy.index(max(List_copy))
          

          0 分配给Highest_value

          List_copy[Highest_position] = 0
          

          然后再次运行您的线路。

          Second_Highest = max(List_copy)
          

          【讨论】:

          • 几乎没有必要复制整个列表。我强烈建议阅读此问题的其他一些答案。
          【解决方案12】:

          我知道这个话题很老,但这里有一个解决这个问题的简单方法。针对 heapq.nlargest 进行了测试,这有点快(不需要排序):

          适用于正数和负数。

          函数如下:使用的最大时间:0.12,使用的最大内存:29290496 heapq.nlargest:使用的最大时间:0.14,使用的最大内存:31088640

          def two_highest_numbers(list_to_work):
          
              first = None
              second = None
          
              for number in list_to_work:
                  if first is None:
                      first = number
                  elif number > first:
                      second = first
                      first = number
                  else:
                      if second is None:
                          second = number
                      elif number > second:
                          second = number
          
          return [first, second]
          

          【讨论】:

            【解决方案13】:

            另一种仅使用基本 Python 函数的解决方案如下所示:

            >>> largest = max(lst)
            >>> maxIndex = lst.index(largest)
            >>> secondLargest = max(max(lst[:maxIndex]), max(lst[maxIndex+1:]))
            

            如果我们围绕它的最大数字拆分一个列表,我们知道第二大数字要么在左半边,要么在右半边。因此,我们可以通过简单地在列表的左右半部分中找到最大数字中的较大者来轻松找到第二大数字。

            证明这是 O(n) 时间和 O(1) 空间是微不足道的。我们遍历列表一次以找到最大的元素,然后再次找到第二大的元素。我们只存储最大值本身和最大值的索引。

            【讨论】:

              【解决方案14】:

              对列表进行排序,如果列表不为空,则提取最后两个元素

              >>> a=[0,6,8,5,10,5]
              >>> a.sort()
              >>> a
              [0, 5, 5, 6, 8, 10]
              >>> if a:
              ...  print a[-1],a[-2]
              ... 
              10 8
              

              简单且最高效:)

              现在如果不需要排序,找max,去掉max,再找max

              >>> a=[0,6,8,5,10,5]
              >>> max(a)
              10
              >>> a.remove(max(a))
              >>> max(a)
              8
              >>> 
              

              当然,您会丢失原始列表,但您也可以创建一个临时列表。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-28
                • 2011-05-26
                • 2022-07-23
                • 2015-03-13
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多