算法通关村——海量数据处理办法

从40个亿中产生一个不存在的整数

题目:给定一个输入文件,包含40亿个非负整数,请设计一个算法,产生一个不存在该文件中的整数,假设你由1GB的内存来完成任务。

用位图去存储大数据

在数据量很大时,采用位方式(俗称位图)存储数据是常用的思路。

我们可以使用bit map的方式来表示数出现的情况。具体来说,是申请一个长度为4 294 967 295(32位无符号整数的十进制最大值,即0xFFFFFFFF)的bit类型的数组bitArr(就是boolean类型),bitArr上的每个位置只可以表示0或1状态,0表示没有这个数,1表示有这个数。在遍历40亿个无符号数时,遇到所有的数时,就把bitArr相应的位置的值设置为1。例如遇到1000,就将bitArr[1000]设置为1。8个bit为1B,所以长度为4 294 967 295的bit类型的数组占用500MB空间,这就满足题目给定的要求了。

我们可以先遍历一次数据,将所有的数都存入位图中,再依次遍历bitArr,如果哪个位置上的值没被设置为1,这个数就不在40亿个数中。例如,发现bitArr[8001] = 0,那么8001就是没出现过的数,遍历完bitArr后,所有没出现过的数就都找出来了。

位存储的核心是:我们存储的不是这40亿个数据本身,而是其对应的位置。

用10MB来存储

把上面的问题进阶一下,如果只用10MB的内存,那么位图也不能搞定了,需要另寻他法。

这里使用分块的思想,时间换空间,通过两次遍历来搞定。

40亿 / 8字节 = 5亿字节,大约0.5GB也就是500MB的空间才能存下所有的数。如果只有10MB的空间,至少需要50个块才可以。

一般来说,划分都是使用2的整数倍,因此划分为64个块是合理的。

首先,将0~4 294 967 295这个范围平均划分为64个区间,每个区间是67 108 864个数,例如:

  • 第0 区间(0~67 108 863)
  • 第 1 区间(67 108 864~134 217 728)
  • 第 i 区间(67 108 864´I~67 108 864´(i+1)-1)
  • ……
  • 第 63 区间(4 227 858 432~4 294 967 295)

因为一共只有40亿个数,所以如果统计落在每一个区间上的数有多少,肯定有至少一个区间上的计数少于67 108 864。利用这一点可以找到其中一个没出现过的数。具体过程如下:

第一次遍历,先申请长度为 64 的整型数组 countArr[0…63],countArr[i]用来统计区间 i 上的数有多少。遍历 40 亿个数,根据当前数是多少来决定哪一个区间上的计数增加。例如,如果当前数是 3 422 552 090 , 3 422 552 090/67 108 864=51 , 所以第 51 区间上的计数增加countArr[51]++。遍历完 40 亿个数之后,遍历 countArr,必然会有某一个位置上的值(countArr[i]) 小于 67 108 864,表示第 i 区间上至少有一个数没出现过。我们肯定会找到至少一个这样的区间。

此时使用的内存就是countArr 的大小(64*4B),是非常小的。

假设找到第 37 区间上的计数小于 67 108 864,那么我们对这40亿个数据进行第二次遍历:

  1. 申请长度为 67 108 864 的 bit map,这占用大约 8MB 的空间,记为 bitArr[0…67108863]。
  2. 遍历这 40 亿个数,此时的遍历只关注落在第 37 区间上的数,记为 num(num满足num/67 108 864==37),其他区间的数全部忽略。
  3. 如果步骤 2 的 num 在第 37 区间上,将 bitArr[num - 67108864*37]的值设置为 1,也就是只做第 37 区间上的数的 bitArr 映射。
  4. 遍历完 40 亿个数之后,在 bitArr 上必然存在没被设置成 1 的位置,假设第 i 个位置上的值没设置成 1,那么 67 108 864´37+i 这个数就是一个没出现过的数。

总之就是先分块,根据内存限制去决定分块的数量,以及统计区间的大小,即第二次遍历的时候bitArr大小,然后利用区间计数的方式,找到哪个计数不足的区间,这个区间上肯定有没出现的数,最后最这个区间做位图映射,遍历一遍后就能找到一次都没出现的数了。

你可能感兴趣的:(超大规模数据处理,算法)