海量数据处理
1. 散列分治
对于海量数据,由于没办法将其一次性装入内存进行处理,不得不将其通过散列映射的方法分割成相应的小块数据,然后再对各个小块数据通过hash_map进行统计或其他操作。
1.1 寻找 Top K 问题
提取出某日访问百度次数最多的那个IP
解决问题三个步骤:
- 分而治之/散列映射。现将该日访问的IP全部提取出来,逐个写入到一个大文件中,然后采取散列映射的方式如
(hash(IP)%1000)
,把大文件的数据映射到1000个小文件中。 - hash_map统计,用hash_map(ip,value)分别对1000个小文件的IP进行频率统计,找出每个小文件中出现频率最高的IP,最后得到1000个IP。
- 堆/快速排序。统计出1000个频率最高的IP后,根据他们的频率大小进行排序。得到最高的IP。
一般策略是:分而治之/散列映射(如有必要)+hash_map+堆
2. 多层划分
因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后在一个可以接受的范围内查找。
2.1 寻找中位数
找出5亿个int型整数的中位数
- 将5亿个整数分成2^16个区域,读取数据落在各个区域里的个数。
- 根据统计结果就可以判断出落到那个区域,并知道区域里第几大是中位数。
- 再次扫描落在这个区域里的那些数就可以了。
3. MapReduce
4. 外排序
需要处理大量数据,但内存没办法全部放下,数据只能存放在硬盘上。外排序采取的一种“排序-归并”策略。
- 排序阶段,先读入能放在内存中的数据,将其拍下后输出到一个临时文件中。依次进行,将排序数据组织为多个有序的临时文件。
- 归并阶段,将这些临时文件组合成一个大的有序文件,即排序结果。
5. 位图
用一个位(bit)来标记某个元素对应的值,而键就是该元素。采用位为单位,可以大大节省空间。
5.1 电话号码统计
文件中包含一些电话号码,每个号码8位,统计不同号码的个数。
8位号码最多99 999 999个号码,大概99M位,采用位图,1表示存在,0表示不存在,最后统计个数。
5.2 2.5亿个整数去重
2.5亿个整数找出不重复的数,内存中不足以存放这2.5亿个整数
采用二位图(每个数分配2位,00表示不存在,01表示出现一次,10表示出现多次,11无意义),读取某个数,如果00变成01,01则变成10,最后输出01的个数。
5.3 整数快速排序
读入数据,存在置1,不存在则保持为0,最后依次输出为1的数字。
6. 布隆过滤器
Bloom Filter是一种空间效率很高的随机数据结构。
- 其结构为长度为n的位数组,初始化全为0。当一个元素被加入集合中时,通过k个散列函数将这个元素映射成一个位数组中的k个点,并将这k个点全部置1。
- 检查一个元素是否在集合中时,只要看这个元素被映射成位阵列的k个点是不是都是1,就能大致判断这个元素是否在集合中。如果存在某个点为0,则该元素一定不再集合中,如果都为1,该元素可能在集合中。
布隆过滤器存在一定的误差,不适合零误判的应用场合,因为有可能是其他元素让改变了相应位的值。适合可以容忍一定的误判率的,比如垃圾邮件过滤。
7. Trie树
Trie树,线索树,单词查找树或键树。是一种树型结构,常用于统计和排序大量字符串等场景中(但不局限与字符串),经常被搜索引擎用于文字词频统计。它的优点是最大程度的减少无谓的字符串比较,查询效率较高。
它具有三个性质:
1. 根节点不包含字符,除根节点外每一个节点都包含一个字符
2. 从根节点到某一节点的路径上经过的字符连接起来,即为该节点对应的字符串
3. 每个节点的所有子节点包含的字符都不相同
故每一层的节点数都是26^i级别的,顺着根节点向下找,如果该节点被标记,则存在,否者不存在,则标记,即插入该元素,这样一来,插入和查询操作可以一起实现。