Algorithm 查找一个非常大的文件的k个最大元素(而k非常大)
假设我们有一个非常大的文件,其中包含数十亿个整数,我们想找到这些值的Algorithm 查找一个非常大的文件的k个最大元素(而k非常大),algorithm,large-files,Algorithm,Large Files,假设我们有一个非常大的文件,其中包含数十亿个整数,我们想找到这些值的k最大元素 棘手的是k本身也非常大,这意味着我们不能在内存中保留k元素(例如,我们有一个包含100亿个元素的文件,我们希望找到100亿个最大的元素) 我们如何在O(n)中做到这一点 我的想法是: 我们开始读取文件,并用另一个文件检查它,该文件保持k最大元素(按递增顺序排序),如果读取元素大于第二个文件的第一行,我们将删除第一行并将其插入第二个文件,时间复杂度将为O(NlogK)(如果我们可以随机访问该文件,否则它将是'O(Nk)
k
最大元素
棘手的是k
本身也非常大,这意味着我们不能在内存中保留k
元素(例如,我们有一个包含100亿个元素的文件,我们希望找到100亿个最大的元素)
我们如何在O(n)
中做到这一点
我的想法是:
我们开始读取文件,并用另一个文件检查它,该文件保持k
最大元素(按递增顺序排序),如果读取元素大于第二个文件的第一行,我们将删除第一行并将其插入第二个文件,时间复杂度将为O(NlogK)
(如果我们可以随机访问该文件,否则它将是'O(Nk)'
如果你想在
O(n)
中这样做,我想如果我们有选择算法的外部版本(快速排序中的分区算法),我们就可以在O(n)中这样做
但我在任何地方都找不到它使用随机选择来找到文件中第K个最大的元素。你可以通过线性多次传递输入来完成这项工作,只要它不比内存大太多。然后将所有至少和它一样大的元素都转储掉。PS:我对K的定义不同。它是一个很小的数字,比如说2、100或1000。这里m对应于OPS对k的定义。对此表示抱歉
取决于您可以对原始数据执行多少次读取,以及您拥有的空间有多大。此方法假设您拥有与原始数据等效的额外空间
步骤1:在整个数据中选取K个随机数
步骤2:对K个数字进行排序(假设索引从1到K)
步骤3:创建K+1个单独的文件,并将它们命名为0到K
步骤4:对于数据中的每个元素,如果它在第i个元素和第i+th个元素之间,则将其放入第i个文件中。
第5步:根据每个文件的大小,选择将包含第m个编号的文件。
步骤6:使用新文件和新m重复所有操作(新m=m-所有较低文件的大小之和)
关于最后一步,如果K=2,m=1000,文件0的大小是800,1是900,2是200,new_m=m-800=200,迭代地处理文件1。如果所有的值都是不同的,或者我们可以忽略双精度,并且我们有32位整数,我只需为每个可能的值使用一位(需要2^32位=2^29字节=512兆字节)(应该适合你的公羊)
用0初始化512MB
线性读取文件时(O(n))为每个读取值设置相应的位
最后查找第一个k设置位以获得k个最大值。(O(2^32)位测试)
如果这些值不明显,并且您想知道这些值出现的频率,您可以添加第四步,再次读取文件并计算在前三步中找到的值出现的次数。这仍然是O(n)使用标准的合并类型算法,您可以非常轻松地做到这一点
假设你有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亿个数字。每个块按降序排序
现在创建一个最大堆。将每个块的第一个编号添加到堆中。还必须添加块编号或编号在文件中的位置,以便读取下一个编号
然后:
而选择的数量<100亿
selected=heap.remove()
++所选数量
将所选内容写入输出
从所选块中读取下一个数字并放置在堆上
这有点复杂,跟踪数字来自哪个区块,但也不算太糟
最大堆从不包含超过100个项目(基本上,每个块一个项目),因此在第二个过程中内存不是问题。通过一点工作,您可以通过为每个块创建一个较小的缓冲区来避免大量读取,这样您就不会为所选的每个数字产生磁盘读取成本
它基本上只是一个磁盘合并排序,但有一个早期输出
第一遍的复杂度是b*(m log m)
,其中b是块的数量,m是块中的项目数量。N是文件中的项目总数,等于b*m
。第二遍的复杂度是k log b
,其中k
是要选择的项目数量,b是块的数量。您可以通过保持ma的最小堆来实现这一点x尺寸k
- 每次新数字到达时-检查堆是否小于
k
,如果小于,则添加它
- 如果不是-检查最小值是否小于新元素,如果是,则将其弹出并插入新元素
完成后,就有了一个包含k个最大元素的堆。
这个解决方案是O(nlogk)复杂度,其中n是元素数,k是您需要的元素数
- 也可以使用选择算法在O(n)中完成-存储所有元素,然后找到
(k+1)第
个最大的元素,并返回比它大的所有元素。但这更难实现,并且对于合理大小的输入-可能不会更好。此外,如果流包含重复项,则需要更多的处理
你所说的随机选择是什么意思?你是说随机访问文件吗?(例如,在Java中使用随机访问文件)从数组中随机抽取一个小样本。找到样本的中值。过滤掉或存储掉数组中中值错误一侧的所有内容。重复此操作,直到
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