【问题标题】:Find the most frequent number in an array, with limited memory查找数组中出现频率最高的数,但内存有限
【发布时间】:2014-01-17 17:15:12
【问题描述】:

如何找到数组中出现频率最高的数?数组可以非常大,例如 2GB,而我们只有有限的内存,比如 100MB。

我正在考虑外部排序,它是排序而不是复制彼此相邻的数字。或哈希玛。但不知道如何处理有限的内存。而且我什至不确定外部排序是否是一个好主意。

【问题讨论】:

  • 你可以想到就地排序,然后遍历数组。但是它会破坏你原来的数组。
  • @JudyJiang 有数字的范围吗?
  • 这里也看看我的回答 (stackoverflow.com/a/23441932/191148)

标签: arrays algorithm sorting


【解决方案1】:

在最坏的情况下,除了一个出现两次的数字之外,您的所有数字都是不同的,除非您将两个重复的数字同时加载到主内存中,否则无法在主内存中检测到这一点,这不太可能如果您的总数据大小远大于主内存大小,则无需排序。在这种情况下,最好的办法是批量排序数字并以文件的形式保存到磁盘,然后执行合并排序合并步骤,一次将所有排序的文件读入内存几行,并输出合并的排序列表到一个新文件。然后,您按顺序浏览汇总的排序文件,并计算您看到每个数字的次数,跟踪哪个数字出现的次数最多。

如果您假设最频繁的数字是 50% 或更高的频率,那么您可以做得更好。您可以通过仅通过数字列表一次来解决持续额外内存的问题。基本上,您首先将最常见的值 (MCV) 初始化为第一个数字,并将计数器 N 初始化为 1。然后遍历列表。如果列表中的下一个数字是 MCV,则将 N 加一。否则将 N 减 1。如果 N 为 0 且下一个数字与 MCV 不同,则将 MCV 设置为新数字并将 N 设置为 1。很容易证明这将以存储在 MCV 中的最常见值终止.

【讨论】:

    【解决方案2】:

    假设:

    • 整数是 4 个字节。
    • 在 2 GB 数组中有少于 (100 MB / 4 B) = (104857600 / 4) = 26214400 个不同整数。每个数字都映射到 0-26214399 索引范围内。

    让我们做直方图。

    1. 在我们的 100 MB 空间中创建存储桶。它是一个名为histogram 的整数数组,最多可以存储26214400 个计数器。计数器最初设置为 0。
    2. 遍历 2 GB 数组一次。当您阅读x 时,请执行histogram[x]++。
    3. 在histogram 中找到最大值,遍历它一次。如果最大值为histogram[i],则i 是最频繁的数字。

    瓶颈是第 2 步,遍历 2 GB 数组,但我们只做了一次。

    如果第二个假设不成立(即有超过 26214400 个不同的整数):

    为索引从 0 到 26214399 的数字制作直方图。保留直方图中出现频率最高的数字。为索引从 26214400 到 52428798 的数字制作直方图。保留直方图中最频繁的数字和之前最频繁的数字。以此类推。

    在最坏的情况下,如果有 2^32 个不同的数字,它将在该 2 GB 数组上执行 (2^32 / 26214400 + 1) = 164 次迭代。

    一般来说,它会执行 (NUMBER_OF_DISTINCT_NUMBERS / 26214400 + 1) 次迭代。

    【讨论】:

    • @Downvoter 您能否发表评论(以便我可以改进我的答案)?
    • 您的第一个建议假定人们确切知道存在哪些数字 - 否则可能无法将出现的数字双射到 0-26214399 范围内,即使只有很少的不同数字数字。您的第二个建议效率非常低(考虑例如操作实际上具有 64 位数字的情况......)
    【解决方案3】:

    如果您的内存有限,但合理的处理能力和超快的速度不是问题,根据您的数据集,您可以尝试以下方法:

    遍历数组,计数从 1 到 1000 的数字。保留计数最多的那个。然后数 1001 到 2000。保持这些之间的最大计数,以及第一批中最大的计数。重复直到计算完所有数字。

    我相信根据您的数据集的具体情况,对此有很多优化。

    【讨论】:

    • 这行不通。具有最高计数的单个数字很可能在没有最高计数的范围内。例如,如果您的数字在 0-100 范围内,并且您将它们分成 10 个桶。假设数字 21 出现 3 次,它是 0-20 范围内的唯一数字。但是从 10 到 19 的每个数字都出现一次。您将捡起 10 的水桶并扔掉 20 的水桶。
    • 我想你误会了。你从 1 到 10 数每个数字。假设 5 出现 3 次,所以你保持 5=3。然后你数从 11 到 20 的每一个数字,17 出现 2 次。你保持最高的运行最高(5 = 3)和最新的最高(17 = 2)所以你保持5 = 3。这有意义吗?
    • 我确实误解了你的方法。你描述的会起作用。我试图撤消我的反对票,但它表示必须先编辑答案。
    【解决方案4】:

    假设 4 字节整数,您可以将 (100 / 4) = 25MB 整数放入可用内存中。

    1. 通读您的大文件,计算 0 ... 25MB-1 范围内的每次出现次数和数字。使用大数组来累积计数。

    2. 找出出现频率最高的数字,存储该数字及其频率并清空数组。

    3. 通读大文件,对 25MB ... 50MB-1 范围内的数字重复计数过程。

    4. 找出新数组中出现频率最高的数字。将其与您在步骤 2 中存储的数字/频率进行比较。存储频率较高的数字/频率并清除数组。

    5. 起泡、冲洗、重复。

    ETA:如果我们可以假设只有一个答案,即没有两个具有相同最高频率的不同数字,那么如果特定范围的数组显示平局,您可以丢弃所有数字。否则,为每个范围存储获胜者的问题会变得更加复杂。

    【讨论】:

      【解决方案5】:

      根据假设,更好的方法可能是使用 MJRTY 算法:

      http://www.cs.utexas.edu/~moore/best-ideas/mjrty/

      或其概括:

      http://www.cs.yale.edu/homes/el327/datamining2011aFiles/ASimpleAlgorithmForFindingFrequentElementsInStreamsAndBags.pdf

      这个想法是,只要有两个变量(一个计数器和一个值存储),您就可以确定是否存在多数元素(严格出现超过 50% 的时间),该元素是什么。泛化需要 (k+1) 个计数器和值存储来查找出现 100/k% 的元素。

      因为这些只是多数的候选者(如果有 k 多数元素,就是它们;但如果没有 k 多数元素,那么这些只是偶然的随机元素),第二次通过data 可以帮助您获得候选人的确切数量,并确定哪一个(如果有的话)是多数人。

      这是非常快速和高效的内存。

      几乎没有其他优化,但使用 4kb 内存,您应该能够以较高的概率找到 2GB 数据的多数元素 - 取决于您拥有的数据类型。

      【讨论】:

        猜你喜欢
        • 2017-07-18
        • 2011-09-09
        • 1970-01-01
        • 2019-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多