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