2010-08-26 39 views
35

有一个文件包含10G(1000000000)个整数,请查找这些整数的中值。你得到2G内存来做到这一点。任何人都可以想出一个合理的方式?谢谢!访问问题:从整数的整数中找到中值

+1

整数有多大?这是一个10G文件,其中包含以文本或二进制格式存储的整数? – 2010-08-26 06:48:06

+0

整数的数量是否已知? – 2010-08-26 07:06:29

+0

我已更新我的问题,请检查它。 @Will A:整数与计算机可以表示的一样大。 @ abhin4v:是的,因为我已经更新了我的问题,它的10G(1000000000) – didxga 2010-08-26 07:11:29

回答

35

创建一个8字节的数组,其长度为2^16个条目。拿出你的输入数字,移出底部的16位,并创建一个直方图。

现在,您在该直方图中计数,直到达到覆盖值中点的bin。

再次通过,忽略所有不具有相同顶部位集的数字,并且创建底部位的直方图。

通过该直方图进行计数,直到达到覆盖(整个值列表)中点的bin。

现在您知道中位数,在O(n)时间和O(1)空间(实际上,在1 MB以下)。

下面是一些示例Scala代码,这是否:

def medianFinder(numbers: Iterable[Int]) = { 
    def midArgMid(a: Array[Long], mid: Long) = { 
    val cuml = a.scanLeft(0L)(_ + _).drop(1) 
    cuml.zipWithIndex.dropWhile(_._1 < mid).head 
    } 
    val topHistogram = new Array[Long](65536) 
    var count = 0L 
    numbers.foreach(number => { 
    count += 1 
    topHistogram(number>>>16) += 1 
    }) 
    val (topCount,topIndex) = midArgMid(topHistogram, (count+1)/2) 
    val botHistogram = new Array[Long](65536) 
    numbers.foreach(number => { 
    if ((number>>>16) == topIndex) botHistogram(number & 0xFFFF) += 1 
    }) 
    val (botCount,botIndex) = 
    midArgMid(botHistogram, (count+1)/2 - (topCount-topHistogram(topIndex))) 
    (topIndex<<16) + botIndex 
} 

这里是工作的一个小组输入数据:

scala> medianFinder(List(1,123,12345,1234567,123456789)) 
res18: Int = 12345 

如果你有存储64个整数,你可以相反,在4遍中使用相同的策略。

+0

聪明。我喜欢。 – Patrick 2010-08-26 15:40:21

+0

不错!这个比我的好,并且有代码! – ajduff574 2010-08-26 20:17:09

+0

复杂性令人印象深刻。有点让我想起基数排序/桶排序的想法。 – 2010-08-27 09:36:31

4

如果文件是文本格式,只需在读入文件时将其转换为整数即可,因为作为字符存储的整数可能比存储为整数的整数需要更多的空间取决于整数的大小和文本文件的类型。编辑:你编辑你的原始问题;现在我可以看到你无法将它们读入内存中,请参阅下文。

如果您无法读取它们到内存中,这就是我想出了:

  1. 弄清楚你多少整数有。你可能从一开始就知道这一点。如果不是,那么它只需要通过文件一次。假设这是S.

  2. 使用你的2G内存找出x个最大的整数(无论你能容纳多少)。你可以在文件中进行一次传递,在某种排序列表中保留x最大,随着时间的推移丢弃其余的剩余部分。现在你知道第x个最大的整数。除了第x个,我可以称之为x1,你可以放弃所有这些。

  3. 再做一遍,找到下一个x最大的整数小于 x1,最小的是x2。

  4. 我想你可以看到我要去的地方。经过几次后,您将读取(S/2)中最大的整数(您必须记录您找到的整数数量),这是您的中位数。如果S是偶数,那么你会平均中间的两个。

+0

@ ajduff574:因为我更新了我的问题,有10G(1000000000)整数 – didxga 2010-08-26 07:16:56

+1

+1令人印象深刻。但我预见到一个处理大量重复数字的问题。想象一下,通过文件的一半,你的2G排序存储阵列填满了。在整个后半段,你不会遇到任何使你从2G阵列中驱逐元素的数字,但是你遇到了很多与数组中最小元素(x1)完全相同的数字。你到最后,放弃你的名单,开始下一步,并意识到你不知道你以前丢弃了哪些x1,哪些是你没有放弃的。 – advait 2010-08-26 07:29:24

+2

可能的解决方案可能不是在这种情况下使用x1并使用x1 + 1,从而丢弃2G阵列中大于或等于x1 + 1的所有内容。但是,您可能会达到您的整个2G阵列变得均匀的点。然后怎样呢?你不能丢弃任何数字! – advait 2010-08-26 07:31:58

3

对文件进行遍历并找到整数和最小和最大整数值的数量。

取最小值和最大值的中点,并获得中点任意一侧的值的最小值和最大值 - 再次读取文件。

分区计数> count =>中位于该分区内。

对分区重复考虑“分区向左”的大小(易于维护),并且还要注意min = max。

我确定这个工作也适用于任意数量的分区。

+0

令人印象深刻的,在我看来,这应该采取N * log(N)时间,并使用O(1)内存(具有极低的常量)。 – 2013-04-21 11:54:27

+0

非常好! @avl_sweden - 请注意,它不是真正的N * log(N),因为日志不在数组中的元素数上,而是在数字范围内! 所以基本上,对于64位整数,它是N * log(2^64), 又名64 * N :) – ZeDuS 2013-10-19 18:33:35

3
  1. 对文件进行磁盘上external mergesort对整数进行排序(如果尚未知道,则对其进行计数)。
  2. 一旦文件被排序,寻找到中间数字(奇数),或平均文件中的两个中间数字(即使是例子)以获得中位数。

使用的内存量是可调整的,不受原始文件中整数数量的影响。外部排序的一个警告是中间排序数据需要写入磁盘。

鉴于n =原始文件整数数量:

  • 运行时间:O(nlogn)
  • 内存:O(1),可调
  • 盘:O(n)
12
+2

+1,10G和2G之间的5倍差异因子听起来像这是预期的答案。 – 2010-08-26 22:42:14

+0

@Ants Aasma,10G整数通常是40GB,即2GB或RAM的20倍。不过Medians的Medians仍然可以工作。 – grokus 2010-08-27 01:46:26

+0

啊,是的,就这样。我原本误解为10GB的整数。 – 2010-08-27 10:42:10

1

查看托本的方法在这里:。它也在文档底部的C中实现。

0

我最好猜测,中位数的概率中位数是最快的。方药:

  1. 采取下一组N个整数(N应该是足够大的,说1000或10000元)
  2. 然后计算这些整数位,并将其分配给变量X_new。
  3. 如果迭代不是第一 - 计算二位数中位数:

    X_global =(X_global + X_new)/ 2

  4. 当你将看到X_global波动并不多 - 这意味着你发现数据的大致中位数。

但也有一些注意事项:

  • 问题出现了 - 是中间误差可以接受。
  • 整数随机必须以统一的方式进行分配,对于解决工作

编辑: 我打了一下这个算法,改变了一点想法 - 在每次迭代中,我们应该总结X_new随体重,如:从[0.5 .. 1]

k,以及增加在每次迭代:

X_global = K * X_global +(1-K)* X_new。

要点是使中值的计算在极少量的迭代中快速收敛到某个数。因此,只有252次迭代才能在100000000个阵列元素之间找到非常接近的中值(具有大误差)!检查该C实验:

#include <stdlib.h> 
#include <stdio.h> 
#include <time.h> 

#define ARRAY_SIZE 100000000 
#define RANGE_SIZE 1000 

// probabilistic median of medians method 
// should print 5000 as data average 
// from ARRAY_SIZE of elements 
int main (int argc, const char * argv[]) { 
    int iter = 0; 
    int X_global = 0; 
    int X_new = 0; 
    int i = 0; 
    float dk = 0.002; 
    float k = 0.5; 
    srand(time(NULL)); 

    while (i<ARRAY_SIZE && k!=1.) { 
     X_new=0; 
     for (int j=i; j<i+RANGE_SIZE; j++) { 
      X_new+=rand()%10000 + 1; 
     } 
     X_new/=RANGE_SIZE; 

     if (iter>0) { 
      k += dk; 
      k = (k>1.)? 1.:k; 
      X_global = k*X_global+(1.-k)*X_new; 

     } 
     else { 
      X_global = X_new; 
     } 

     i+=RANGE_SIZE+1; 
     iter++; 
     printf("iter %d, median = %d \n",iter,X_global); 
    } 

    return 0; 

} 

哎呀好像我说的是平均数,中位数没有。如果是这样,你需要正确的中位数,而不是指 - 忽略我的帖子。无论如何,平均数和中位数都是非常相关的概念。

祝你好运。

0

这是由@Rex Kerr描述的算法,用Java实现。

/** 
* Computes the median. 
* @param arr Array of strings, each element represents a distinct binary number and has the same number of bits (padded with leading zeroes if necessary) 
* @return the median (number of rank ceil((m+1)/2)) of the array as a string 
*/ 
static String computeMedian(String[] arr) { 

    // rank of the median element 
    int m = (int) Math.ceil((arr.length+1)/2.0); 

    String bitMask = ""; 
    int zeroBin = 0; 

    while (bitMask.length() < arr[0].length()) { 

     // puts elements which conform to the bitMask into one of two buckets 
     for (String curr : arr) { 
      if (curr.startsWith(bitMask)) 
       if (curr.charAt(bitMask.length()) == '0') 
        zeroBin++; 
     } 

     // decides in which bucket the median is located 
     if (zeroBin >= m) 
      bitMask = bitMask.concat("0"); 
     else { 
      m -= zeroBin; 
      bitMask = bitMask.concat("1"); 
     } 

     zeroBin = 0; 
    } 

    return bitMask; 
} 

一些测试用例和算法的更新可以找到here