40亿个32位整数求中位数

题目:

40亿个32位整数,想找到其中的中位数该怎么办?

条件

按行读取文件,读取操作不占用内存。

应该具备的能力:

2^k = ? 应该都能够熟记,达到反射性反应的程度。

字节数对应计算机中的容量(T, G, M, K)

1K = 2^10 ≈ 10^3,一千

1M = 2^20 ≈ 10^6,一百万

1G = 2^30 ≈ 10^9,十亿

分析简要思路:

int型为有符号32位整数,占4个字节,取值范围在-2,147,483,648~2,147,483,647之间。-21亿到21亿

uint型为无符号32位整数,占4个字节,取值范围在0~4,294,967,295之间。0到42亿

使用桶排序的思想,将全部整数按照固定的数值区间划分到桶中,划分区间的范围取决于桶的数量和整数的数量,记录每个桶中存放的整数个数,然后逐个获取桶中存放的整数个数相加,一直得到结果超过20亿,则中位数在当前该桶A中,记录偏移量S,即在桶A的第S个整数为正好第20亿个;

如果当前该桶A只是粗略估计某个数值区间的整数总数,则需要再对这个区间细分到每个桶保存,然后再对所有整数全部遍历一次,将所有原本落在桶A的整数再细分到每个桶中,然后再根据偏移量S来获取更细化的桶,再看这个桶是否还是粗略估计区间,如果是,继续划分。。。。。。

32位整数有正有负,所以我们在记录的时候可以全部加上21亿,当成无符号整数来统计,最后取出中位数时再减去21亿

前置步骤:

1、确定记录者的数值类型:

40亿个32位整数,所以一个数最多出现40亿次,可以用一个uint32整数来记录次数

有些题目问的是比如10G的32位整数,那么每个占4字节,所以10G共有2.5G个整数,大概就是25亿整数

2、确定桶的数量:

允许的最大内存 / 记录者所占的内存大小 = 桶的数量

也可以理解为创建一个元素为“记录者数值类型”,数量为“桶的数量”的数组

3、确定桶的区间:

32位整数最大区间(42亿)/桶的数量 = 桶的区间

解答,内存分情况:

1、内存2GB

桶的数量为2GB/4B = 512M,所以创建512M个桶,即内存允许我们创建大概是5亿个桶

每个桶的区间为42亿 / 5亿 ≈ 8

所以我们在2GB内存下,可以将42亿的大区间划分成约5亿个小区间,每个小区间大小为8

所以遍历全部整数:

第一个桶记录取值为0到7的数的个数,第二个记录取值为8到15的数的个数,以此类推。。。

如一个数为0,则arr[0/8]++,一个数为8,则arr[8/8]++

最终每个桶相加,找到相加后超出20亿的桶,记录偏移量,标记在该桶内的第几个是中位数

比如是在第二个桶,记录的取值范围为8到15,偏移量为30

则释放掉原来所有的桶,重新创建8个桶,第一个桶记录取值为8的数的个数,第二个桶记录9。。。

再进行一次遍历,只统计取值为8到15的数的个数,保存到各个桶中

然后找到相加和超过30的桶,该桶记录的值即为中位数

2、内存:10MB

桶的数量为10MB/4B = 2.5M,大概是250万个桶,即内存允许我们创建250万个桶

每个桶的区间为42亿 / 250万 = 1680

所以我们在10MB内存下,可以将42亿的大区间划分成250万个小区间,每个小区间大小为1680

第一个桶记录取值为0到1679的数的个数,第二个记录取值为1680到3359的数的个数,以此类推。。。

第一次遍历找到中位数所在桶后,第二次遍历则重新创建1680个桶,每个桶精确到1个数

3、内存:20KB

桶的数量为20KB/4B = 5K,即内存允许我们创建5000个桶

每个桶的区间为42亿 / 5000 = 84万

所以我们在20KB内存下,可以将42亿的大区间划分成5000个小区间,每个小区间大小为84万

现在问题来了,第一次遍历找到中位数所在桶后,第二次遍历仍然创建不了84万个桶,我们的内存只允许创建5000个,那就继续创5000个呗。

新的桶区间为84万 / 5000 = 168

重新创建5000个桶,再将84万的大区间划分成5000个小区间,每个小区间大小为168

仍然能通过上次的偏移量获取新桶的位置,新桶仍然是模糊统计,再细分创建168个桶,这次每个桶精确到1个数

当内存开始逐渐变小的时候,第二次划分桶就能精确到1个数的情况就不复存在,需要再划分的次数就会逐渐增加

那么极限情况在哪?

4、内存超级小:8B,只有两个uint32给你

这是内存小到最极限的情况

桶的数量为8B/4B = 2,即内存允许我们创建2个桶

每个桶的区间为42亿 / 2 = 21亿

当只有两个uint32变量来做桶时,桶排序的思想就变成了二分的思想。

还是一样,遍历全部整数:

对42亿区间进行二分,将-21亿到0的部分划到第一个桶,将1到21亿的部分划到第二个桶

如果第一个桶统计的值超过20亿,则说明中位数在第一个桶中,继续对范围-21亿到0的部分二分划分到两个桶,然后再二分,以此类推。。。

因此,极限情况是最多划分32次,就能求出中位数

5、内存无限大(一般也不会这么问)

这是内存大到最极限的情况,如果内存足够大,则桶的数量可以创很多,多到超过42亿,

则每个桶的区间为42亿/42亿 = 1

每个桶可以直接精确到1个数,那么桶排序直接转化为计数排序,创建一个42亿大小的uint32数组arr将int型整数的所有范围都涵盖,每遍历一个整数num就直接arr[num]++即可

遍历所有桶的值进行相加,求出第20亿个整数即可。

总结

1、确定记录者的数值类型

2、确定桶的数量

3、确定桶的区间

4、在内存不够用的情况下,先模糊统计中位数所在区间,再对该区间放大,还不够再放大,直到最后能精确统计某个数值的个数为止。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值