美团一面:如何在 100 亿数据中找到中位数?

 

海量数据中找到中位数,内存肯定是无法一次性放下这么多数据的

中位数定义:数字排序之后,位于中间的那个数。比如将 100 亿个数字进行排序,排序之后,位于第 50 亿个位置的那个数就是中位数。

 

2、解题思路一
一个无符号整数的大小为4B,则100亿个数的大小为40GB,如果内存够大的话可以对这100亿个数加载到内存中,然后使用堆排序或者快速排序进行排序,取出中位数即可。使用快排时,每次划分之后只需要比较枢纽值的索引和50亿比较,然后只对两个划分中的一个进行递归排序即可,而不用整体进行排序。使用堆排时,建立堆之后,需要进行50亿次的调整即可。

3、解题思路二
中位数问题可以看做一个统计问题,而不是排序问题,无符号整数大小为4B,则能表示的数的范围为为0 ~ 2^32 - 1(40亿),则可以用一个2^32(4GB)大小的数组(也叫做桶)来保存100亿个数中每个无符号数出现的次数。遍历这100亿个数,当元素值等于桶元素索引时,桶元素的值加1。当统计完100亿个数以后,则从索引为0的值开始累加桶的元素值,当累加值等于50亿时,这个值对应的索引为中位数。时间复杂度为O(n)。

4、解题思路三
如果内存的大小小于4GB时(假设内存为512M),这时怎么办呢?解决方案为使用区间的桶排序,解题思路如下:

(1)如果只有512M的内存,则512M内存可以装2^(9 + 10 + 10) = 536,870,912个无符整数,约为5亿左右,接着把无符号整数的范围0~40亿划分为每10个数一个区间,也就有4亿个区间,划分后第一个区间0~9,第二个区间10~19,......在内存中使用4亿个数来保存100亿个数中落在每个区间的整数个数。此时内存中还可以存放1亿个数,分100次把100亿个数加载到内存,每次加载1亿个,统计落到每个区间的整数个数。

(2)第一步完成统计之后,可以知道落到4亿个区间中每个区间的整数个数,然后从最小区间向最大区间开始累加,当累加的数达到50亿时,记住这个区间起点位置和终点位置(终点 - 起点 + 1 = 10)和没有加这个区间统计个数时的整数个数。

(3)知道第50亿个数所落在的区间起点和终点位置后,接着对区间的每个数设置一个桶(这里总共为10个桶),用来统计每个数的元素个数。接着对100亿个数分20批次进行遍历,每次加载到内存5亿个数。

(4)统计完成之后就可以知道落到区间内的每个元素个数,接着对区间的统计个数进行累加,当这个累加值加上(2)中保存的没有加上该区间的整数个数等于50亿时,该数对应的索引就位中位数。

总结:整个过程需要遍历100亿个数两次,第一次确定第50亿个数所落在的索引区间,第二次确定第50亿个数所落在的索引。区间大小是一个可以优化的值,优化之后可以使得I/O的次数最少。


原文链接:https://blog.csdn.net/MOU_IT/article/details/88586193

桶排序

1)创建多个小文件桶,设定每个桶的取值范围,然后把海量数据元素根据数值分配到对应的桶中,并记录桶中元素的个数

2)根据桶中元素的个数,计算出中位数所在的桶(比如 100 亿个数据,第 1 个桶到第 18 个桶一共有 49 亿个数据,第 19 个桶有 2 亿数据,那么中位数一定在第 19 个桶中),然后针对该桶进行排序,就可以求出海量数据中位数的值(如果内存还是不够,可以继续对这个桶进行拆分;或者直接用 BitMap 来排序)

简单用 100 个数据画个图直观理解下:

分治法 + 基于二进制比较

假设这 100 亿数据都是 int 类型,4 字节(32 位)的有符号整数,存在一个超大文件中。

将每个数字用二进制表示,比较二进制的【最高位】 (第 32 位),如果数字的最高位为 0,则将这个数字写入 file_0 文件中;如果最高位为 1,则将该数字写入 file_1文件中。

最高位为符号位,也就是说 file_1 中的数都是负数,而 file_0 中的数都是正数。

通过这样的操作,这 100 亿个数字分成了两个文件,假设 file_0 文件中有 60 亿个数字,而 file_1 文件中有 40 亿个数字。

这样划分后,思考一下:所求的中位数在哪个文件中?

100 亿个数字的中位数是 100 亿个数排序之后的第 50 亿个数,现在 file_0 有 60 亿个正数,file_1 有 40 亿个负数,file_0 中的数都比 file_1 中的数要大,排序之后的第 50 亿个数是中位数,那么这个中位数一定位于 file_0 中,并且是 file_0 文件中所有数字排序之后的第 10 亿个数字。

现在,我们只需要处理 file_0 文件了(不需要再考虑 file_1 文件)。

而对于 file_0 文件,可以同样地采取上面的措施处理:将 file_0 文件依次读一部分到内存,将每个数字用二进制表示,比较二进制的【次高位】(第 31 位),如果数字的次高位为 0,写入 file_0_0 文件中;如果次高位为 1 ,写入 file_0_1 文件中。

现假设 file_0_0 文件中有 30 亿个数字,file_0_1 中也有 30 亿个数字,则中位数就是:file_0_1 文件中的数字从小到大排序之后的第 10 亿个数字。

抛弃 file_0_0 文件,继续对 file_0_1 文件 根据【次次高位】(第 30 位) 划分,如此反复下去

posted on 2024-07-25 15:43  myf008  阅读(63)  评论(0编辑  收藏  举报

导航