算法通关村 —— 海量数据的热门算法题

文章探讨了如何在海量数据中寻找不存在的整数、找到出现次数最多的数、识别重复URL以及查找出现两次的数,涉及位图、分块策略和哈希函数在内存受限情况下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

海量数据的热门算法题

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

1.1 位图存储大数据

1.2 使用 10MB 来存储

1.3 确定分块的区间

2. 用2GB 内存在 20 亿个整数中找到出现次数最多的数

3. 从 100 亿个 URL 中查找的问题

4.40 亿个非负整数中找到出现两次的数


海量数据的热门算法题

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


题目要求: 给定一个输入文件,包含40亿个非负整数,请设计一个算法,产生一个不存在该文件中的整数,假设你有1GB的内存来完成这项任务进阶: 如果只有10MB的内存可用,该怎么办?本题不用写代码,能将方法说清楚就很好了。

1.1 位图存储大数据

如果用哈希表来保存出现过的数,如果 40 亿个数都不同,则哈希表的记录数为 40亿条,存一个 32 位整数需要 4B,所以最差情况下需要 40 亿*4B=160 亿字节,大约需要16GB 的空间,内存不符合要求。

40 亿*4B=160 亿字节,大约需要16GB

40 亿 / 8 字节=5亿字节,大约0.5GB的数组就可以存下40亿个数。

如果数据量很大,采用位方式 (俗称位图)存储数据是常用的思路。我们可以使用 bit map 的方式来表示数出现的情况。申请一个长度为 4 294 967 295 的 bit 类型的数组 bitArr (就是boolean类型)。bitArr 上的每个位置只可以表示 0 或1 状态。8 个bit 为 1B,所以长度为 4 294967 295的 bit 类型的数组占用 500MB 空间。 

然后遍历这40 亿个无符号数,遇到所有的数时就把 bitArr 相应位置的值设置为 1。例如,遇到 1000,就把bitArr[1000]设置为1。.遍历完成后,再依次遍历 bitArr,看看哪个位置上的值没被设置为 1,这个数就不在40 亿个数中。例如,发现 bitArr[8001]==0,那么 8001 就是没出现过的数,遍历完 bitArr 之后,所有没出现的数就都找出来了。

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

1.2 使用 10MB 来存储

如果只有 10MB 的内存,此时位图也不能搞定了,我们要另寻他法。

这里我们使用分块思想时间换空间,通过两次遍历来搞定。40亿个数 需要500MB的空间,那如果只有10MB的空间,至少需要50个块才可以。一般我们划分都是使用2的整数倍,因此划分成64个块是合理的。首先,将0~4 294 967 295(2^32) 这个范围平均分成 64 个区间的,每个区间是 67 108 864 个数,例如:
第 0 区间 (0 ~ 67 108 863)
第1区间 (67 108 864 ~ 134 217 728)
第 i 区间 (67 108 864 /~ 67 108 864(i+1)-1)
......
第 63 区间 (4 227 858 432 ~ 4 294 967 295)

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

1)第一次遍历,先申请长度为 64 的整型数组 countArr[0..63],countArr [i] 用来统计区间 i上的数有多少遍历 40 亿个数,根据当前数是多少来决定哪一个区间上的计数增加。例如,如果当前数是 3 422 552 090,3 422 552 090 / 67 108 864=51。所以第 51 区间上的计数增加。

2)遍历完 40 亿个数之后,遍历countArr,必然会有某一个位置上的值 (countArr[i]) 小于 67 108 864,表示第i 区间上至少有一个数没出现过。假设找到第 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 这个数就是一个没出现过的数

总结一下该进阶算法的解法:

1) 根据 10MB 的内存限制,确定统计区间的大小,就是第二次遍历时的 bitArr 大。
2) 利用区间计数的方式,找到那个计数不足的区间,这个区间上肯定有没出现的数。
3) 对这个区间上的数做 bit map 映射,再遍历bit map,找到一个没出现的数即可。

1.3 确定分块的区间

在上面的例子中,采用两次遍历,第一次将数据分成64块刚好解决问题,保证第二次遍历时每个块都能放进这10MB的空间中2^23<10MB<2^24 ,  而 2^23=8388608大约为8MB,也就说我们一次的分块大小只能为8MB左右。所以第二次遍历时如果分为64块,刚好满足要求,所以至少要分成64块,当然如果分成128块、256块等也是可以的。
 

2. 用2GB 内存在 20 亿个整数中找到出现次数最多的数


题目要求: 有一个包含 20 亿个全是 32 位整数的大文件,在其中找到出现次数最多的数
要求,内存限制为 2GB
想要在很多整数中找到出现次数最多的数,通常的做法是使用哈希表对出现的每一个数做词频统计,哈希表的 key 是某一个整数,value 是这个数出现的次数。就本题来说,一共有 20 亿个数,用 32 位的整数也可以表示其出现的次数而不会产生溢出,所以哈希表的 key 需要占用 4B,value 也是4B。那么哈希表的一条记录 (key,value) 需要占用 8B,当哈希表记录数为 2 亿个时,需要至少 1.6GB 的内存。如果 20 亿个数中不同的数超过 2 亿种,内存可能就不够用了。

解决办法是把包含 20 亿个数的大文件用哈希函数分成 16 个小文件,根据哈希函数的性质,同一种数不可能被散列到不同的小文件上,同时每个小文件中不同的数一定不会大于 2 亿种,假设哈希函数足够优秀。然后对每一个小文件用哈希表来统计其中每种数出现的次数,这样我们就得到了 16 个小文件中各自出现次数最多的数还有各自的次数统计。接下来只要选出这16 个小文件各自的第一名中谁出现的次数最多即可。

把一个大的集合通过哈希函数分配到多台机器中,或者分配到多个文件里,分配到多少台机器、分配到多少个文件,可能是由面试官指定,也可能是根据具体的限制来确定,比如本题确定分成 16 个文件,就是根据内存限制 2GB 的条件来确定的。

3. 从 100 亿个 URL 中查找的问题

题目: 有一个包含 100 亿个 URL 的大文件,假设每个 URL 占用 64B,请找出其中所有重复的 URL
补充问题: 某搜索公司一天的用户搜索词汇是海量的 (百亿数据量),请设计一种求出每天热门 Top 100 词汇的可行办法。

解答:原问题的解法使用解决大数据问题的一种常规方法: 把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件,直到划分的结果满足资源限制的要求。首先,你要向面试官询问在资源上的限制有哪些包括内存、计算时间等要求。在明确了限制要求之后,可以将每条 URL 通过哈希函数分配到若千台机器或者拆分成若千个小文件。

例如,1)将 100 亿字节的大文件通过哈希函数分配到 100 台机器上,然后每一台机器分别统计分给自己的 URL 中是否有重复的 URL,哈希函数的性质决定了同一条 URL不可能分给不同的机器;

2)或者在单机上将大文件通过哈希函数拆成 1000 个小文件对每一个小文件再利用哈希表遍历,找出重复的 URL,还可以在分给机器或拆完文件之后进行排序,排序过后再看是否有重复的 URL 出现。

总之,牢记一点,很多大数据问题都离不开分流,要么是用哈希函数把大文件的内容分配给不同的机器,要么是用哈希函数把大文件拆成小文件,然后处理每一个小数量的集合

4.40 亿个非负整数中找到出现两次的数


题目要求: 32 位无符号整数的范围是 0~4 294 967 295,现在有 40 亿个无符号整数,可以使用最多 1GB的内存,找出所有出现了两次的数
本题可以看做第一题的进阶问题,这里将出现次数限制在了两次

首先,可以用 bit map 的方式来表示数出现的情况。申请一个长度为4 294 967 295x2 的bit 类型的数组bitArr,用 2 个位置表示一个数出现的词频。1B 占用 8 个bit,所以长度为 4 294 967 295x2 的 bit 类型的数组占用 1GB 空间

遍历这 40 亿个无符号数,如果初次遇到 num就把bitArr[num*2 + 1]和 bitArr[num*2]设置为 01,如果第二次遇到 num,就把bitArr[num*2+1]和bitArr[num*2]设置为 10,如果第三次遇到 num,就把bitArr[num*2+1]和bitArr[num*2]设置为 11以后再遇到 num,发现此时bitArr[num*2+1]和 bitArr[num*2]已经被设置为 11,就不再做任何设置

遍历完成后,再依次遍历 bitArr,如果发现bitArr[i*2+1]和bitArrli*2]设置为 10,那么i就是出现了两次的数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值