我有一个有100万个号码的文件。我需要知道如何有效地对它进行排序,这样它就不会使计算机失速,并且只打印前10名。
#!/usr/bin/python3
#Find the 10 largest integers
#Don't store the whole list
import sys
def fOpen(fname):
try:
fd = open(fname,"r")
except:
print("Couldn't open file.")
sys.exit(0)
all = fd.read().splitlines()
fd.close()
return all
words = fOpen(sys.argv[1])
big = 0
g = len(words)
count = 10
for i in range(0,g-1):
pos = i
for j in range(i+1,g):
if words[j] > words[pos]:
pos = j
if pos != i:
words[i],words[pos] = words[pos],words[i]
count -= 1
if count == 0:
print(words[0:10])
我知道这是选择排序,我不确定什么是最好的选择。
答案 0 :(得分:30)
如果您只需要前10个值,那么您将浪费大量时间对每个数字进行排序。
只需查看数字列表,并跟踪到目前为止看到的十大最大值。在浏览列表时更新前十名,并在结束时打印出来。
这意味着您只需要在文件中进行一次传递(即theta(n)的时间复杂度)
更简单的问题
您可以将问题看作是在数字列表中查找最大值的概括。如果您被给予{2,32,33,55,13, ...}
并被要求找到最大值,您会做什么?典型的解决方案是遍历列表,同时记住到目前为止遇到的最大数字并将其与下一个数字进行比较。
为简单起见,我们假设我们正在处理正数。
Initialize max to 0
0 < 2, so max = 2
2 < 32, so max = 32
32 < 33, so max = 33
33 < 55, so max = 55
55 > 13, so max = 55
...
return max
所以你看,我们可以在列表的单个遍历中找到最大值,而不是任何类型的比较排序。
<强>泛化强>
在列表中查找前10个值非常相似。唯一的区别是我们需要跟踪前10名而不是最大(前1名)。
底线是你需要一个容纳10个值的容器。当你迭代你庞大的数字列表时,你在10号容器中唯一关心的值就是最小值。那是因为如果您发现了一个值得进入前10名的新号码,这就是要替换的号码。
无论如何,事实证明最适合快速查找分钟的数据结构是最小堆。但是我不确定你是否已经了解了堆,并且使用10个元素的堆的开销可能超过它的好处。
任何容纳10个元素并且可以在合理的时间内获得分钟的容器将是一个良好的开端。
答案 1 :(得分:26)
最好的排序是部分排序,在Python库中可用heapq.nlargest
。
答案 2 :(得分:14)
import heapq
with open('nums.txt') as f:
numbers=map(int,f.readlines())
print heapq.nlargest(10,numbers)
print heapq.nsmallest(10,numbers)
"""
[1132513251, 13252365, 23512, 2000, 1251, 1235, 324, 100, 82, 82]
[1, 1, 7, 13, 15, 21, 22, 22, 33, 82]
"""
答案 3 :(得分:1)
你想要的是一个好的selection algorithm
以下python代码基于函数partition()
partition将列表拆分为两个。小于“pivotValue”的值将移动到列表的开头。大于pivotValue的值将移动到列表的末尾。
这是在O(N)操作中通过从头到尾遍历列表来完成的,每次它查看一个值,它将它移动到列表开头附近,只有当它小于透视值时。
(请注意,在您的情况下,我们实际上将较大的值移动到列表的开头,因为您希望最大值不是最小的值。)
一旦我们在O(N)时间内对列表进行了分区,我们就会在列表的开头留下m个大数字。如果m = 10然后很棒,那就是你的十大数字。如果m大于10,那么我们需要再次对m个最大数字进行分区,以获得m个最大数字中的10个最大数字。如果m小于10,那么我们需要10多个数字,所以我们将righter分区找到10-m数字并将它们添加到我们的m数中以得到我们需要的10个数字。
所以我们继续分区,直到我们有10个最大的数字。这是通过select()
方法完成的。整个方法通常非常快,因为每次我们进行分区时,我们只剩下大约一半的数字来处理。 (如果你经常将你需要看的数字除以2,这很好)。每次我们执行一个产生10个以上大数的分区时,我们就会忽略一大堆太小的数字。
以下是代码:
def partition(_list,left,right,pivotIndex):
pivotValue=_list[pivotIndex]
_list[right],_list[pivotIndex]=pivotValue,_list[right]
storeIndex=left
for i in range(left,right):
if _list[i] > pivotValue:
_list[storeIndex],_list[i]=_list[i],_list[storeIndex]
storeIndex+=1
_list[right],_list[storeIndex]=_list[storeIndex],_list[right]
return storeIndex
from random import randint
def select(_list,left,right,k):
if left==right:
return _list[:left+1]
pivotIndex=randint(left,right)
pivotNewIndex=partition(_list,left,right,pivotIndex)
pivotDist=pivotNewIndex-left+1
if pivotDist==k:
return _list[:pivotNewIndex+1]
elif k<pivotDist:
return select(_list,left,pivotNewIndex-1,k)
else:
return select(_list,pivotNewIndex+1,right,k-pivotDist)
_list=[1,2,109,2234,23,6,1,234,11,4,12451,1]
left=0
right=len(_list)-1
pivotIndex=4
print _list
"[1, 2, 109, 2234, 23, 6, 1, 234, 11, 4, 12451, 1]"
print partition(_list,left,right,pivotIndex) #partition is order(N).
"7" #index 7, so the lowest number are in the first 7 numbers of the list [1, 2, 1, 6, 1, 11, 4, 23]
print _list
"[1, 2, 1, 6, 1, 11, 4, 23, 2234, 109, 12451, 234]"
print select(_list,left,right,10)
"[1, 2, 1, 1, 4, 11, 6, 23, 109, 234]"
with open('nums.txt') as f:
numbers=map(int,f.readlines())
print select(numbers,0,len(numbers)-1,10)
"[1132513251, 2000, 23512, 13252365, 1235, 1251, 324, 100, 82, 82]"