文章目录
0 建议
大数据类型题目技巧
-
哈希函数可以把数据按照种类均匀分流
-
布隆过滤器用于集合的建立与查询,并可以节省大量空间
-
一致性哈希解决数据服务器的负载管理问题
-
利用并查集结构做岛问题的并行计算
-
位图解决某一范围上数字的出现情况,并可以节省大量空间
-
利用分段统计思想、并进一步节省大量空间
-
利用堆、外排序来做多个处理单元的结果合并
1 "40"亿个非负整数中找到未出现的数
原始问题
32
位无符号整数的范围是0~4,294,967,295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然存在没出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数?
哈希表不适合作为存储结构的原因
假设使用哈希表保存出现过的数,如果40亿个数不同,则哈希表的记录数为40亿条,存一个32位整数需要4B,最差情况需要 40 亿 ∗ 4 B = 160 亿 K B ≈ 16 G B 40亿*4B=160亿KB≈16GB 40亿∗4B=160亿KB≈16GB,远远不符合要求
使用位图作为存储结构
使用bit map
表示数出现的情况,申请长度为4,294,967,295的bit
类型数组
b
i
t
A
r
r
bitArr
bitArr,
b
i
t
A
r
r
bitArr
bitArr上每个位置表示0
或1
状态。8bit=1B,长度为4,294,967,295的bit类型数组占用500MB
遍历40亿无符号整数,遇到所有的数时就把
b
i
t
A
r
r
[
]
bitArr[]
bitArr[]相应位置置1
(假设遇到7000,就把
b
i
t
A
r
r
[
7000
]
bitArr[7000]
bitArr[7000]置1
)
遍历完,再次遍历
b
i
t
A
r
r
bitArr
bitArr,哪个位置没有置1
,这个数就不在这40亿数中(假设
b
i
t
A
r
r
[
8000
]
=
0
bitArr[8000]=0
bitArr[8000]=0,那么8000就是没出现过的数)
问题进阶
内存限制为10MB,但是只需找到一个没出现的数即可
0~4,294,967,295是可以平均分成64个区间,每个区间67,108,864个数
第0
区间
(
0
−
67
,
108
,
863
)
(0-67,108,863)
(0−67,108,863)、第1
区间(
67
,
108
,
864
−
134
,
217
,
728
)
67,108,864-134,217,728)
67,108,864−134,217,728)、第i
区间
(
67
,
108
,
864
X
i
−
67
,
108
,
864
X
(
i
+
1
)
−
1
)
(67,108,864 X i-67,108,864 X (i+1) - 1)
(67,108,864Xi−67,108,864X(i+1)−1),…,第63
区间
(
4
,
227
,
858
,
432
4
,
294
,
967
,
295
)
(4,227,858,432~4,294,967,295)
(4,227,858,432 4,294,967,295)
一共有40亿个数,如果统计落在每一个区间上的数有多少,肯定至少一个区间的计数少于67,108,864,利用这点找到其中一个没出现的数
-
第一次遍历先申请长度为64的整型数组 c o u n t A r r [ 0...63 ] countArr[0...63] countArr[0...63], c o u n t A r r [ i ] countArr[i] countArr[i]用来统计区间
i
上的数有多少。 -
遍历40亿个数,根据当前数多少决定在哪个区间上计数增加
如果当前数是 3422 , 552 , 090 3422,552,090 3422,552,090, 3422 , 552 , 090 / 67 , 108 , 864 = 51 3422,552,090/67,108,864=51 3422,552,090/67,108,864=51,所以第
51
区间计数增加 c o u n t A r r [ 51 ] + + countArr[51]++ countArr[51]++。 -
遍历完40亿数后,遍历
countArr
,必然有一个位置 c o u n t A r r [ i ] countArr[i] countArr[i]小于 67 , 108 , 864 67,108,864 67,108,864,表示第i区间至少一个数没出现过此时使用内存 64 ∗ 4 B 64*4B 64∗4B
假设找到第37
区间的计数小于
67
,
108
,
864
67,108,864
67,108,864,以下是第二次遍历
- 申请长度为
67
,
108
,
864
67,108,864
67,108,864的bitmap(约占用8MB),记为
bitArr[0..67,108,863]
- 再遍历一次40亿个数,此时遍历只关注落在第
37
个数,记为 n u m ( n u m / 67 , 108 , 864 = 37 ) num(num/67,108,864=37) num(num/67,108,864=37),其他区间的数全都忽略 - 如果步骤2的num在第
37
区间上,将 b i t A r r [ n u m − 67 , 108 , 864 ∗ 37 ] bitArr[num-67,108,864*37] bitArr[num−67,108,864∗37]的值设置成1。只做第37区间上的数的bitArr映射 - 遍历完40亿个数后,在bitArr上必然存在没有设置成
1
的位置,假设第i
位置上的值没有设置成1
,那么 67 , 108 , 864 X 37 + i 67,108,864 X 37 + i 67,108,864X37+i这个数就是一个没出现过的数
总结
- 根据10MB的内存限制,确定区间统计的大小,就是第二次遍历时的bitArr大小
- 利用区间计数的方式,找到计数不足的区间,这个区间肯定有没出现的数
- 对这个区间的数做bitmap映射,再遍历bitmap,找到一个没出现的数即可
2 找到"100"亿个URL中重复的URL及搜索词汇的TopK问题
[问题1]
有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL
常规思路
把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件,一直进行划分,直到划分的结果满足资源限制
弄清楚资源限制(内存、计算时间),明确要求后将每条URL通过哈希函数分配到若干机器或拆分成若干小文件(若干指具体资源限制计算出精确的数量)
- 例如,将100亿个的大文件通过哈希函数分配到
100
台机器上,然后每台机器分别统计分给自己的URL是否有重复的URL,同时哈希函数的性质决定同一条URL可能分配给不同机器 - 或者,在单机上将大文件通过哈希函数拆成
1000
个小文件,对每一个小文件再利用哈希表遍历,找出重复的URL - 或者,在分给机器或拆完文件之后排序,排序过后再看是否有重复的URL出现
[问题2]
某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天热门Top100
词汇的可行办法
方法1 哈希分流+小根堆
- 把包含百亿数据量的词汇文件分流到不同的机器上,对每台机器来说,如果分到的数据量依然很大(比如内存不够或存在其他问题,再用哈希函数把每台机器的分流文件拆成更小的文件处理)
- 处理每个小文件时,通过哈希表统计每种词及词频
- 哈希表建立完成后,再遍历哈希表,遍历哈希表的过程中使用大小为
100
的小根堆选出每个小文件的Top100
(整体未排序的Top100) - 每个小文件都有自己词频的小根堆(整体未排序的Top100),将小根堆的词按照词频排序,得到排序后的
Top100
- 把各个文件排序后的
Top100
进行外排序或者继续利用小根堆,就可以选出每台机器上的Top100
- 不同机器之间的
Top100
再进行外排序或利用小根堆 - 最终求出整个百亿数据量中的
Top100
方法2 哈希分流+大根堆
- 把包含百亿数据量的词汇文件分流到不同的机器上,对每台机器来说,如果分到的数据量依然很大(比如内存不够或存在其他问题,再用哈希函数把每台机器的分流文件拆成更小的文件处理)
- 处理每个小文件时,通过哈希表统计每种词及词频
总结
分流是处理大数据的不错方案
- 哈希函数将大文件内容分配给不同机器
- 用哈希函数把大文件拆成小文件,处理每个小数量的集合
大数据经典TopK问题
- 哈希函数分流
- 哈希表做词频统计
- 堆结构和外排序处理
思路剖析
哈希分流成小文件>>
小文件词频统计,存储在哈希表中>>
小文件计算出各自的TopK>>
每个小文件的TopK进行汇总排序,得到大文件的TopK>>
每个排序后的大文件的TopK进行排序,得到每台机器的TopK>>
不同机器的TopK再排序>>
得到全局TopK
3 "40"亿个非负整数中找到出现两次的数和所有数的中位数
[问题1] 32位无符号整数的范围是 0 − 4 , 294 , 967 , 295 0-4,294,967,295 0−4,294,967,295,现在有40亿个无符号整数,可以使用最多1GB的内存,找出所有出现两次的数。
算法思路
使用bitmap
,用两个位置表示一个数的词频,01
表示该数出现一次,10
表示该数出现两次,11
表示该数出现三次及以上,1B占用8bit,长度为
4
,
294
,
967
,
295
∗
2
4,294,967,295*2
4,294,967,295∗2的bit型数组占用1GB
算法实现
- 遍历40亿个无符号整数,初次遇到
num
, b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1]和 b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成0
、1
- 第二次遇到
num
, b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1]和 b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成1
、0
- 第三次遇到
num
, b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1]和 b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]设置成1
、1
- 以后再遇到
num
,发现此时 b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1]和 b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]已经被设置成1
、1
,就不再做任何设置 - 遍历完成后,再次遍历
bitArr
,如果发现 b i t A r r [ n u m × 2 + 1 ] bitArr[num × 2+1] bitArr[num×2+1]和 b i t A r r [ n u m × 2 ] bitArr[num × 2] bitArr[num×2]被设置成1
、0
,那么num
就是出现了两次的数
[问题2]
可以使用最多10KB的内存,怎么找到这40亿个整数的中位数?
采用区间统计的方式
- 根据内存大小和无符号整数数组大小确定分配数组长度
- 遍历40亿数据,
a
r
r
[
i
]
arr[i]
arr[i]表示第
i
区间有多少数 - 通过累加每个区间的出现次数,能找到第20亿个数落在哪个区间
- 比如
0~k-1
区间上的数出现了18亿,当发现加上第k
区间的数超过了20亿,就可以知道第20亿数是第k
区间上的数,并且可以知道是第k
区间上的第2亿个数 - 接下来申请单个区间长度
L
的无符号整型数组 - 遍历40亿个数,只关心处在第
k
区间的数,记为num
,将 c o u n t [ n u m − k ∗ L ] + + count[num-k*L]++ count[num−k∗L]++(对第k
区间做词频统计) - 最后只在第
k
区间找第2亿个数即可