【发布时间】:2011-02-13 21:46:08
【问题描述】:
如何从包含 100,000 个整数的列表中检索最高的两个项目,而不必先对整个列表进行排序?
【问题讨论】:
如何从包含 100,000 个整数的列表中检索最高的两个项目,而不必先对整个列表进行排序?
【问题讨论】:
您遍历列表,维护包含迄今为止遇到的最高和第二高项目的值的变量。遇到的每个新项目都将替换新项目高于的两者中的任何一个(如果有的话)。
【讨论】:
遍历整个列表是不排序的唯一方法。
【讨论】:
如果不对列表进行排序,唯一真正做到这一点的方法是遍历整个列表并保存最高的两个数字。我认为你最好对列表进行排序。
【讨论】:
这会起作用,但我不知道您是否要保留列表中的项目:
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 次,实际上只需要一次迭代。
第二高的项目是一个相当简单的案例,但是对于第 k 高的项目,您想要的是 selection algorithm。该页面非常详尽,因此最好只是阅读它。
【讨论】:
一个非常巧妙的方法是使用heapq。 Heapify the array (O(n)),然后只需弹出许多您需要的元素 (log(n))。 (在一次采访中看到这个问题,很好的问题要记住。)
【讨论】:
使用heapq.nlargest。如果您想处理的不仅仅是前两个元素,这是最灵活的方法。
这是一个例子。
>>> import heapq
>>> import random
>>> x = range(100000)
>>> random.shuffle(x)
>>> heapq.nlargest(2, x)
[99999, 99998]
【讨论】:
nlargest(2, x) 与 sorted(x, reverse=True)[:2])
您可以期待的最佳时间是线性的,因为您至少必须查看所有元素。
这是我解决问题的伪代码:
//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)
【讨论】:
JacobM's answer 绝对是要走的路。但是,在实施他所描述的内容时,需要牢记一些事项。这是一个在家玩的小教程,可指导您完成解决此问题的棘手部分。
如果此代码用于生产,请使用列出的更有效/更简洁的答案之一。这个答案是针对刚接触编程的人。
这个想法很简单。
largest 和 second_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。
让我们将largest 和second_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。largest 变为 1。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)
太棒了。
现在让我们用一个负数列表来试试吧。
inlist = [-1, -2, -3] # CHANGED!
让我们运行它。
(0, 0)
这根本不对。这些零是从哪里来的?
事实证明,largest 和 second_largest 的起始值实际上大于列表中的所有项目。您可能考虑的第一件事是将largest 和second_largest 设置为Python 中可能的最低值。不幸的是,Python 没有最小的可能值。这意味着,即使您将它们都设置为 -1,000,000,000,000,000,000,您也可以得到一个小于该值的列表。
那么最好的办法是什么?让我们尝试将largest 和second_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)
等等,什么?
让我们再次单步执行我们的逻辑。
largest 设置为 -3second_largest 设置为 -2在那儿等着。已经,这似乎是错误的。 -2 大于 -3。这是导致问题的原因吗?让我们继续吧。
largest 设置为 -1; second_largest 设置为 largest 的旧值,即 -3是的,这看起来是个问题。我们需要确保largest和second_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 次以上)。
【讨论】:
heapq.nlargest的时候写了一个谩骂:)
“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 个的唯一值添加防护措施。
【讨论】:
将您的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)
【讨论】:
我知道这个话题很老,但这里有一个解决这个问题的简单方法。针对 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]
【讨论】:
另一种仅使用基本 Python 函数的解决方案如下所示:
>>> largest = max(lst)
>>> maxIndex = lst.index(largest)
>>> secondLargest = max(max(lst[:maxIndex]), max(lst[maxIndex+1:]))
如果我们围绕它的最大数字拆分一个列表,我们知道第二大数字要么在左半边,要么在右半边。因此,我们可以通过简单地在列表的左右半部分中找到最大数字中的较大者来轻松找到第二大数字。
证明这是 O(n) 时间和 O(1) 空间是微不足道的。我们遍历列表一次以找到最大的元素,然后再次找到第二大的元素。我们只存储最大值本身和最大值的索引。
【讨论】:
对列表进行排序,如果列表不为空,则提取最后两个元素
>>> 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
>>>
当然,您会丢失原始列表,但您也可以创建一个临时列表。
【讨论】: