有一个包含10G(1000000000)个整数的文件,请找到这些整数的中位数。你有2G内存来做这件事。任何人都可以想出一个合理的方式吗?谢谢!
答案 0 :(得分:37)
创建一个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次传递中使用相同的策略。
答案 1 :(得分:12)
答案 2 :(得分:4)
如果文件是文本格式,您可以将内容放入内存中,只需将内容转换为整数即可,因为存储为字符的整数可能比整数存储的整数占用更多空间,取决于整数的大小和文本文件的类型。编辑:您编辑了原始问题;我现在可以看到你无法将它们读入内存,见下文。
如果你无法将它们读入内存,我就会想到这一点:
计算出你有多少整数。你可能从一开始就知道这一点。如果没有,那么它只需要一次通过该文件。我们假设这是S。
使用2G的内存来查找x个最大的整数(无论多少都可以)。您可以在文件中进行一次传递,将x最大值保存在某种排序列表中,随时丢弃其余部分。现在你知道了第x个最大的整数。你可以丢弃所有这些,除了第x个最大的,我称之为x1。
进行另一次传递,找到下一个x最大的整数小于 x1,其中最小的是x2。
我想你可以看到我的目标。几次通过后,您将读入(S / 2)最大整数(您必须跟踪您找到的整数),这是您的中位数。如果S是偶数,那么你将平均两个中间值。
答案 3 :(得分:3)
传递文件并查找整数计数以及最小和最大整数值。
取最小值和最大值的中点,并获取中点两侧值的计数,最小值和最大值 - 再次读取文件。
分区计数&gt; count =&gt;中位数位于该分区内。
重复分区,考虑到“左侧分区”的大小(易于维护),还要观察min = max。
我相信这也适用于任意数量的分区。
答案 4 :(得分:3)
使用的内存量是可调整的,不受原始文件中整数数量的影响。外部排序的一个警告是需要将中间排序数据写入磁盘。
给定n
=原始文件中的整数数:
O(nlogn)
O(1)
,可调节O(n)
答案 5 :(得分:1)
在这里查看Torben的方法:http://ndevilla.free.fr/median/median/index.html。它还在文档底部的C中实现。
答案 6 :(得分:0)
我最好的猜测中位数的概率中位数是最快的。配方:
如果迭代不是第一次 - 计算两个中位数的中位数:
X_global =(X_global + X_new)/ 2
当您看到X_global波动不大时 - 这意味着您找到了近似的数据中位数。
但有一些注意事项:
修改强> 我已经用这个算法玩了一下,改变了一点想法 - 在每次迭代中我们应该将X_new与减重相加,例如:
X_global = k * X_global +(1.-k)* X_new:
k来自[0.5 .. 1.],并且每次迭代都会增加。
要点是计算中值,以便在极少量的迭代中快速收敛到某个数。因此,仅在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;
}
Opps似乎在谈论平均而不是中位数。如果是这样,你需要确切的中位数,而不是意味着 - 忽略我的帖子。在任何情况下,均值和中位数都是非常相关的概念。
祝你好运。答案 7 :(得分:0)
这是由Java实现的@Rex Kerr描述的算法。
/**
* 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。
答案 8 :(得分:0)
我也被问到了同样的问题,我无法给出确切的答案,所以在面试之后,我浏览了一些有关面试的书,这就是我从《破解编码》面试书中发现的东西。
示例:数字是随机生成的,并存储在(扩展的)数组中。怎么样 您会跟踪中位数吗?
我们的数据结构头脑风暴可能类似于以下内容:
•链接列表?可能不会。链接列表在访问和 排序数字。
•数组?也许可以,但是您已经有一个数组。你能以某种方式保留元素吗 排序?那可能很贵。让我们暂缓此操作,然后在需要时返回。
•二叉树?这是可能的,因为二叉树在排序方面做得很好。实际上,如果二叉搜索树完全平衡,则顶部可能是中位数。但是请注意,如果元素数量为偶数,则中位数实际上是平均值 中间的两个元素。中间的两个元素不能同时位于顶部。这可能是一个可行的算法,但让我们回到它上面。
•堆?堆真的很擅长基本排序并跟踪最大和最小。 这实际上很有趣-如果您有两个堆,则可以跟踪更大的堆 元素的一半和较小的一半。较大的一半保留在最小堆中,例如 表示较大的一半中的最小元素位于根。较小的一半保留在 最大堆,使得较小一半的最大元素位于根。现在,有了 在这些数据结构中,您的根有潜在的中位数元素。如果 堆的大小不再相同,您可以通过弹出来快速“重新平衡”堆 一个元素从一个堆中移出并推到另一个堆上。
请注意,您做的问题越多,对哪些数据的直觉就越发展 适用的结构。您还将开发出更精细的本能,以了解哪种方法最有用。