在生成一组数字时查找第N个最大值

时间:2009-07-05 16:28:35

标签: memory-management selection

我正在编写一个程序而不是需要在一组数字中找到第N个最大值。这些数字是由程序生成的,但我没有足够的内存来存储N个数字。有没有比N更好的上限可以实现存储?数字组(和N)大小的上限约为100,000,000。

注意:数字是小数,列表可以包含重复数。

[编辑]:我的内存限制是16 MB。

5 个答案:

答案 0 :(得分:3)

你能从头开始重新生成同一组数字吗?如果是,您可以对输出进行多次传递:首先查找最大值,重新启动生成器,找到小于该值的最大数字,重新启动生成器,然后重复此操作直到获得结果。

这将是一个真正的性能杀手,因为你有很多数字并且需要很多传球 - 但是在内存方面,你只需要存储2个元素(当前最大值和“限制”) ,你在上一次传球中找到的号码)和传球计数器。

你可以通过使用你的优先级队列来找到M个最大的元素(选择一些你能够适应内存的M)来加快速度,让你减少N / M所需的传球次数。

如果您需要查找15个数字列表中的第10大元素,您可以通过相反的方式来节省时间。由于它是第10大元素,这意味着有15-10 = 5个元素小于这个元素 - 所以你可以寻找第6个最小的元素。

答案 1 :(得分:3)

这是一种多遍算法(因此,您必须能够多次生成相同的列表,或将列表存储到二级存储中)。

第一遍:

  • 找到最高值和最低值。这是你的初始范围。

在第一次之后传递:

  1. 将范围分成10个等距离的分档。我们不需要在箱子中存储任何数字。我们只是计算垃圾箱的成员资格。所以我们只有一个整数数组(或bigints - 无论什么都可以准确地保持我们的计数)注意10是一个任意选择的二进制数。您的样本量和分布将决定最佳选择。
  2. 旋转数据中的每个数字,增加您所看到的数字中任何一个数字的数量。
  3. 找出哪个垃圾箱有您的答案,并在该垃圾箱上方添加多少数字到胜出垃圾箱上方的数字计数。
  4. 获奖区的顶部和底部范围是您的新范围。
  5. 再次循环执行这些步骤,直到您有足够的内存来容纳当前bin中的数字。
  6. 最后一遍:

    • 您现在应该知道当前垃圾箱的数量是多少。
    • 您有足够的存储空间来获取当前垃圾箱范围内的所有数字,因此您可以旋转并获取实际数字。只需对它们进行排序并获取正确的数字。

    示例:如果您看到的范围是0.0到1000.0,那么您的容器范围将为:

     (- 0.0 - 100.0]
     (100.0 - 200.0]
     (200.0 - 300.0]
     ...
     (900.0 - 1000.0)
    

    如果您通过计数找到您的号码在(100.0 - 2000.0)箱中,您的下一组箱将是:

     (100.0 - 110.0]
     (110.0 - 120.0]
     etc.
    

    另一个多遍想法:

    只需进行二分查找。选择范围的中点作为第一个猜测。您的传递只需要执行高于/低于计数以确定下一个估计(可以通过计数加权,或者简单的平均值来简化代码)。

答案 2 :(得分:1)

这类似于另一个问题 - C Program to search n-th smallest element in array without sorting? - 你可以在那里得到一些答案 该逻辑同样适用于第N个最大/最小搜索 注意:我并不是说这与此重复。


由于您有很多(近10亿?)数字,这是空间优化的另一种方式 让我们假设您的数字符合32位值,因此大约10亿将需要接近32GB的空间。现在,如果你能负担大约128MB的工作记忆,我们可以一次性完成。

  1. 想象一下,将10亿位向量存储为32位字的数组
    • 将其初始化为全零
    • 开始浏览您的号码并继续为号码值
    • 设置正确的位位置
    • 当您完成一次传递时,从第N个设置位的位向量的开头开始计数
    • 该位的位置为您提供第N个最大数字的值
    • 您实际上已对流程中的所有数字进行了排序(但是,未跟踪重复项的数量)

答案 3 :(得分:0)

如果我理解的话,程序的上限内存使用量是O(N)(可能是N + 1)。您可以维护一个生成值列表,该列表大于当前X(目前为第N个最大值)排序的最低值。只要生成了一个新的更大的值,就可以用列表的第一个元素替换当前的X,并将刚刚生成的值插入到列表中的相应位置。

答案 4 :(得分:0)

sort -n | uniq -c和Nth应该是第N行