查找非常大的文件的k个最大元素(而k非常大)

时间:2013-07-01 17:37:58

标签: algorithm large-files

假设我们有一个非常大的文件,其中包含数十亿个整数,我们希望找到这些值的k个最大元素,

棘手的部分是k本身也非常大,这意味着我们无法将k元素保留在内存中(例如,我们有一个包含100个Billon元素的文件,我们希望找到10个十亿最大元素)

我们如何在O(n)中执行此操作?

我的想法:

我们开始阅读文件,我们用另一个保存k最大元素的文件(按递增顺序排序)检查它,如果读取元素大于第二个文件的第一行,我们删除第一个文件我们将它插入到第二个文件中,时间复杂度为O(NlogK)(如果我们可以随机访问该文件,否则它将是'O(Nk)'

任何想法都在O(n)中执行此操作,我想如果我们有Selection algorithm的外部版本(快速排序中的分区算法),我们可以在O(n)执行此操作,但我无法在任何地方找到它

6 个答案:

答案 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是你需要的元素的数量。

  • 也可以使用选择算法在O(n)中完成 - 存储所有元素,然后找到(k+1)th最大元素,并返回比它更大的所有元素。但实施起来比较困难,并且合理的尺寸输入 - 可能不会更好。此外,如果流包含重复项,则需要进行更多处理

答案 3 :(得分:2)

如果所有值都是 distinct 或者我们可以忽略doublets并且我们有32位整数,我只需要为每个可能值使用一位(需要2 ^ 32位= 2 ^ 29字节= 512兆字节(应该适合你的RAM))。

  1. 使用0
  2. 初始化512MB
  3. 线性读取文件( O(n))时,为每个读取值设置相应的位。
  4. 最后查找第一个 k 设置位以获得k个最大值。 ( O(2 ^ 32)位测试)
  5. 如果值不是 distinct ,并且您想知道值的发生频率,则可以添加第4步,再次读取文件并计算找到的值的出现次数前三个步骤。那仍然是 O(n)

答案 4 :(得分:0)

  • 我们可以使用大小为“k”的 PriorityQueue。
  1. 继续添加 PriorityQueue 中的值。
  2. 如果大小超过 k,则删除第一个元素。 PriorityQueue 默认按升序排序。
  3. 在 PriorityQueue 中添加所有元素后,我们可以稍后弹出该元素以获得第 k 个最大的元素。

答案 5 :(得分:-1)

使用随机选择来查找文件中的第k个最大元素。你可以在输入上进行线性多次传递,只要它不比内存大很多倍。然后只丢弃至少和它一样大的所有内容。