资源限制类问题的常用解决方案
资源限制类问题的常用解决方案
作者:Grey
原文地址:
问题1
32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40 亿个无符号整数的文件,可以使用最多 1GB 的内存,怎么找到出现次数最多的数?
主要思路
首先,需要考虑最差情况,假设 40 亿个数都不一样。
如果使用哈希表,key 表示其中的某个数(4 byte),value 表示出现的次数(4 byte),
所以哈希表的每条记录至少需要 8 byte 空间,即:
4Byte + 4Byte = 8Byte
最差情况,40 亿条不同记录进入哈希表,
需要内存空间是:
8Byte * 40亿 = 320亿Byte
约等于 32GB 。内存不够。
所以无法直接使用哈希表来解这个问题。
如果申请数组来存,数组 i 号位置的表示 i 这个值出现的次数。
最差情况,要存下 40 亿个数,数组需要的内存空间是:
40亿 * 4Byte = 160亿Byte
约等于 16G , 内存也不够。
所以,也无法用直接数组来解决这个问题。
所以,我们只能从限制条件入手,
由于只有 1GB 内存,如果用哈希表,一条记录大约 8Byte ,保守估计,1GB 内存可以装下
1G / 8 Byte = 10亿Byte / 8Byte = 1.25亿
,即1.25亿条记录,在这个 1.25亿基础上再保守一点,减少到 1千万,
在 1GB 内存限制下,使用哈希表处理 1千万条不同的记录,绝对不会超过现有的内存限制。通过如下计算
40亿 / 1千万 = 400
可以创建 400 个空文件,然后,对于 40亿个数中每一个数 m,通过哈希函数得到一个哈希值,假设 m 的哈希值为 n, 然后用 n 执行如下公式
hash(m) % 400 = n % 400 = i
得到的 i 的值是多少,就把 m 这个值分配到第 i 号文件中。
根据哈希函数的性质,相同的数一定进入同一个文件。 且 400 个文件中每个文件大约都是 1千万种。
使用哈希表统计每个文件中出现次数最多的数(最多1千万种左右,哈希表在现有资源限制下无压力),所有文件中出现次数最多的数,就是整个文件出现次数最多的数。(因为同一种数的哈希值一样,分配到的文件一定是同一个!)
问题2
32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数, 可以使用最多 1GB 的内存,怎么找到所有未出现过的数?
如果使用HashSet
,大约需要
40亿 * 4Byte = 160亿Byte
大约 16GB ,内存不够。
所以无法直接使用HashSet
来解。
本题可以使用位图(bit数组)来存每个数是否出现,0 表示出现, 1 表示没有出现。以 Java 为例,一个 int 类型可以表示 32 位二进制, 因为要标识每个数是否出现过。所以 40亿个数大约需要
40亿 / 4byte = 1000MB
1000MB 空间的内存占用,满足限制条件。
参考如下示例代码(179这个数字是否出现过):
int[] arr = new int[10];
int i = 179;
int status = (arr[i / 32] & (i << (i % 32 ))) == 0 ? 0 : 1
status = 0
则表示没有出现过,status = 1
则表示没有出现过。
原理就是:i 号 bit 表示 i 这个数是否出现过,用 i/32
标识这个 bit 出现在数组的哪个位置上,用 i%32
来标识这个 bit 属于 arr[某位]
的哪个位置上。
位图的详细说明见:位图的使用与实现
问题3
32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数, 内存限制为 3KB ,只用找到一个没出现过的数即可。
以 Java 为例,3KB 如果用来表示 long 类型(一个 long 类型是 8byte)的数组,数组最大长度大约是:
3KB / 8Byte = 375
。
数组长度大约 375, 然后寻找比 375 小的离 375 最近的二的某次方的数,得到 256 , 接下来可以申请一个 256 长度的 long 类型数组,假设叫 arr 。
由于无符号整数数字一共有 2 的 32 次方个,可以将
2^32 / 256 = 16,777,216
均分成 256 份,使用问题2中位图的思路
arr[0]
统计 0 ~ 16,777,215
出现的次数。
arr[1]
统计 16,777,216 ~ (16,777,216 + 16,777,215)
出现的次数。
……
arr[255]
统计 (2^32 - 1 - 16,777,215) ~ 2^32 - 1
出现过的数。
由于现在的数个数是 40 亿, 不到 2 的 32 次方,所以,肯定有某个位置上的arr
值不够 16,777,216 个。 由于只需要找到一个没有出现过的数,所以只需要在不够 16,777,216 这个范围的位置上进行再一次的 256 份的划分,然后再次使用上述逻辑,直到划分到某个数单独作为一个范围同时没出现过,这个数就是我们需要找的数。
问题4
32位无符号整数的范围内有 4294967295 个数,现在有一个正好包含 40 亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数, 只能使用有限几个变量,如何找到一个没出现过的数(找到一个即可)。
参考问题3,我们可以设置一个变量 L 定位第 0 个数,设置变量 R 定位 2^32-1 上的数,设置 M 变量定位到中间位置。由于一共有 2^32 次方个数,所以统计左边和右边都应该有 2^31 个数,但是总共 40 亿个数,所以必然有一边不满足 2^31 方个数,然后不满足的这一边继续二分,重复上述逻辑,即可找到没有出现过的一个数字。
问题3 和问题4 类似,我们可以得到一个结论: 如果内存3KB,就用256分,如果是几个变量,就用二分。
问题5
有一个包含 100 亿个 URL 的大文件,假设每个 URL 占用 64B,请找出其中所有重复的 URL。
如果允许失误率,这个问题可以使用布隆过滤器来解决。
如果不允许失误,则可以使用问题1的方法,使用哈希函数结合取模操作,分到小文件再判断重复。
问题6
32位无符号整数的范围内有 4294967295 个数,现在有 40 亿个无符号整数,可以使用最多 1GB 的内存,找出所有出现了两次的数。
问题2中,拿一个 bit 来表示一个数出现过一次或者没出现过,本问题我们可以拿两个 bit 来表达一个数出现的次数。
00:表示没出现
01:表示出现过 1 次
10:表示出现过 2 次
11:表示出现过 3 次及 3 次以上。
经过以上处理,只需要看哪些是 10 状态的数。
问题7
32位无符号整数的范围内有 4294967295 个数,现在有 40 亿个无符号整数, 可以使用最多 3KB 的内存,怎么找到这 40 亿个整数的中位数?
参考问题3的做法,把整个 2^32 个数均分到一个 256 长度的 long 型数组中,每个位置管理 16,777,216 个数出现的次数。 然后逐个累加区间中数字出现的次数,一直累加到 21 亿左右,即可判断,中位数一定存在这个区间中。
问题8
32位无符号整数的范围内有 4294967295 个数,有一个 10GB 大小的文件,每一行都装着这种类型的数字,整个文件是无序的,给你 5GB 的内存空间,请你输出一个10GB 大小的文件,就是原文件所有数字排序的结果。
主要思路
以 Java 为例,定义一个数据结构
class Node {
long value;
long times;
}
value 表示文件中的数值,times 表示文件中的数值出现的次数。
然后设置一个大根堆存 Node 。value大的数值在堆顶, 假设堆大小我们设置为3,每次遍历的数和次数加入大根堆。大根堆满了以后,如果新加入一个大根堆中没有的数,则比较新加入的数和大根堆堆顶元素,如果比堆顶元素小,则剔除堆顶元素,加入新元素。遍历一轮以后,大根堆中的三个数一定是整个文件中最小的三个数。然后把这三个数和次数依次写入新文件,然后继续上述遍历和处理。每次都可以拿到三个排序后的数值,直接加入到新文件即可。
所以,因为本问题中有 5GB 内存,根据我们上述的方案,绰绰有余。
问题9
某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门 Top100 词汇的可行办法。
参考问题1,我们可以使用哈希函数结合取模操作,分到小文件思想。
然后使用大根堆,统计每个文件中的 top100。
最后,把每个文件对应的大根堆的堆顶元素弹出放入新的一个大根堆(假设这个大根堆叫 superHeap )中。然后从这个新的大根堆的堆顶弹出堆顶元素,即为全局top1。
然后看这个弹出的堆顶元素是来自于哪个文件,继续把这个文件对应的大根堆堆顶元素弹出,继续放入 superHeap 中,然后从 superHeap 弹出堆顶元素,就是全局top2。
.....
依次类推,直到 top100。
资源限制技巧汇总
- 布隆过滤器用于集合的建立与查询,并可以节省大量空间。
- 一致性哈希解决数据服务器的负载管理问题。
- 利用并查集结构做岛问题的并行计算。
- 哈希函数可以把数据按照种类均匀分流。
- 位图解决某一范围上数字的出现情况,并可以节省大量空间。
- 利用分段统计思想、并进一步节省大量空间。
- 利用堆、外排序来做多个处理单元的结果合并。
更多
参考资料
本文来自博客园,作者:Grey Zeng,转载请注明原文链接:https://www.cnblogs.com/greyzeng/p/15371414.html