假设我们有一个非常大的文件,其中包含数十亿个整数,我们希望找到这些值的k
个最大元素,
棘手的部分是k
本身也非常大,这意味着我们无法将k
元素保留在内存中(例如,我们有一个包含100个Billon元素的文件,我们希望找到10个十亿最大元素)
我们如何在O(n)
中执行此操作?
我的想法:
我们开始阅读文件,我们用另一个保存k
最大元素的文件(按递增顺序排序)检查它,如果读取元素大于第二个文件的第一行,我们删除第一个文件我们将它插入到第二个文件中,时间复杂度为O(NlogK)
(如果我们可以随机访问该文件,否则它将是'O(Nk)'
任何想法都在O(n)
中执行此操作,我想如果我们有Selection algorithm
的外部版本(快速排序中的分区算法),我们可以在O(n)
执行此操作,但我无法在任何地方找到它
答案 0 :(得分:9)
您可以使用标准的合并类型算法轻松完成此操作。
假设您有1000亿个数字,而您想要前100亿。我们会说你可以随时在内存中保存10亿个数字。
所以你传球:
while not end of input
read 1 billion numbers
sort them in descending order
save position of output file
write sorted numbers to output file
然后,您有一个文件,其中包含100个块,每个块包含10亿个数字。每个块按降序排序。
现在创建一个最大堆。将每个块的第一个数字添加到堆中。您还必须在文件中添加块编号或编号的位置,以便您可以读取下一个编号。
然后:
while num_selected < 10 billion
selected = heap.remove()
++num_selected
write selected to output
read next number from the selected block and place on heap
涉及到一点点复杂性,跟踪数字来自哪个块,但它并不太糟糕。
最大堆永远不会包含超过100个项目(基本上每个块一个项目),因此内存在第二次传递中不是问题。通过一些工作,您可以通过为每个块创建一个小的缓冲区来避免大量读取,这样就不会为每个选定的数字产生磁盘读取的成本。
它基本上只是一个磁盘合并排序,但是提早出局。
第一遍的复杂度为b * (m log m)
,其中b是块数,m是块中的项数。 N,文件中的项目总数等于b * m
。第二遍的复杂度为k log b
,其中k
是要选择的项目数,b是块数。
答案 1 :(得分:3)
PS:我对K的定义不同。它是一个较小的数字,例如2或100或1000.这里m对应于OPS对k的定义。对此抱歉。
取决于您可以对原始数据执行的读取次数以及您拥有的空间量。此方法假设您有相当于原始数据的额外空间。
步骤1:在整个数据中选择K个随机数
步骤2:对K数进行排序(假设指数从1到K)
第3步:创建K + 1个单独的文件,并将它们命名为0到K
第4步:对于数据中的每个元素,如果它在第i个和第i个元素之间,则将其放在第i个文件中
步骤5:根据每个文件的大小,选择将具有第m个数字的文件
第6步:使用新文件和新m重复所有内容(new_m = m - sum_of_size_of_all_lower_files)
关于最后一步,如果K = 2,m = 1000,文件0的大小为800,1为900,2为200,new_m = m-800 = 200,并迭代地处理文件1。
答案 2 :(得分:3)
您可以通过维护最大大小为k
的最小堆来实现此目的。
每次有新号码到达时 - 检查堆是否小于k
,如果是 - 添加它。
如果不是 - 检查最小值是否小于新元素,如果是,则将其弹出并插入新元素。
完成后 - 你有一个包含k个最大元素的堆。 这个解决方案是O(nlogk)复杂度,其中n是元素的数量,k是你需要的元素的数量。
(k+1)th
最大元素,并返回比它更大的所有元素。但实施起来比较困难,并且合理的尺寸输入 - 可能不会更好。此外,如果流包含重复项,则需要进行更多处理答案 3 :(得分:2)
如果所有值都是 distinct 或者我们可以忽略doublets并且我们有32位整数,我只需要为每个可能值使用一位(需要2 ^ 32位= 2 ^ 29字节= 512兆字节(应该适合你的RAM))。
如果值不是 distinct ,并且您想知道值的发生频率,则可以添加第4步,再次读取文件并计算找到的值的出现次数前三个步骤。那仍然是 O(n)。
答案 4 :(得分:0)
答案 5 :(得分:-1)
使用随机选择来查找文件中的第k个最大元素。你可以在输入上进行线性多次传递,只要它不比内存大很多倍。然后只丢弃至少和它一样大的所有内容。