编程之法:面试和算法心得 - 海量数据处理(1)

第六章 海量数据处理

本章导读

针对时间问题,可以采用巧妙的算法搭配合适的数据结构(如布隆过滤器、哈希、位图、堆、数据库、倒排索引、Trie树)来解决;而对于**空间问题,可以采取分而治之(哈希映射)**的方法,也就是说,把规模大的数据转化为规模小的,从而各个击破。

针对常说的单机及集群问题,通俗来讲,单机就是指处理装载数据的机器有限(只要考虑CPU、内存、和硬盘之间的数据交互),而集群的意思是指机器有多台,适合分布式处理或并行计算,更多考虑节点与节点之间的数据交互。

一般说来,处理海量数据问题,有以下十种典型方法:
1.哈希分治;
2.simhash算法;
3.外排序;
4.MapReduce;
5.多层划分;
6.位图;
7.布隆过滤器;
8.Trie树;
9.数据库;
10.倒排索引。

分而治之

方法介绍

对于海量数据而言,由于无法一次性装进内存处理,导致我们不得不把海量的数据通过hash映射分割成相应的小块数据,然后再针对各个小块数据通过hash_map进行统计或其它操作。如大数据通过取余的方式映射成小数存放在内存中,或大文件映射成多个小文件

问题实例

1. 海量日志数据,提取出某日访问百度次数最多的那个IP?

**分析:**百度作为国内第一大搜索引擎,每天访问它的IP数量巨大,如果想一次性把所有IP数据装进内存处理,则内存容量明显不够,故针对数据太大,内存受限的情况,可以把大文件转化成(取模映射)小文件,从而大而化小,逐个处理。

换言之,先映射,而后统计,最后排序。

解法:具体分为以下3个步骤

  1. 分而治之/hash映射
    首先把这一天访问百度日志的所有IP提取出来,然后逐个写入到一个大文件中,接着采用映射的方法,比如%1000,把整个大文件映射为1000个小文件。
  2. hash_map统计
    当大文件转化成了小文件,那么我们便可以采用hash_map(ip, value)来分别对1000个小文件中的IP进行频率统计,再找出每个小文件中出现频率最大的IP。
  3. 堆/快速排序
    统计出1000个频率最大的IP后,依据各自频率的大小进行排序(可采取堆排序),找出那个频率最大的IP,即为所求。

注:Hash取模是一种等价映射,不会存在同一个元素分散到不同小文件中去的情况,即这里采用的是%1000算法,那么同一个IP在hash后,只可能落在同一个文件中,不可能被分散的。

2. 寻找热门查询,300万个查询字符串中统计最热门的10个查询

原题:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录,请你统计最热门的10个查询串,要求使用的内存不能超过1G。

分析:这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。

由上面第1题,我们知道,数据大则划为小的,例如一亿个ip求Top 10,可先%1000将ip分到1000个小文件中去,并保证一种ip只出现在一个文件中,再对每个小文件中的ip进行hash_map统计并按数量排序,最后归并或者最小堆依次处理每个小文件的top10以得到最后的结果。

但对于本题,数据规模比较小,能一次性装入内存。因为根据题目描述,虽然有一千万个Query,但是由于重复度比较高,故去除重复后,事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把他们都放进内存中去(300万个字符串假设没有重复,都是最大长度,那么最多占用内存3M*1K/4=0.75G。所以可以将所有字符串都存放在内存中进行处理)。

所以我们放弃分而治之/hash映射的步骤,直接上hash_map统计,然后排序。So,针对此类典型的TOP K问题,采取的对策往往是:hash_map + 堆。

解法

  1. hash_map统计
    先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的hash_map,即hash_map(Query, Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并将Value值设为1;如果该字串在Table中,那么将该字串的计数加1 即可。最终我们在O(N)的时间复杂度内用hash_map完成了统计;
  2. 堆排序
    借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(n) + N’ * O(logk),其中,N为1000万,N’为300万。

当然,你也可以采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。

3. 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词

解法:

  1. 分而治之/hash映射
    顺序读取文件,对于每个词x,取hash(x)%5000,然后把该值存到5000个小文件(记为x0,x1,…x4999)中。这样每个文件大概是200k左右。当然,如果其中有的小文件超过了1M大小,还可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过1M。
  2. hash_map统计
    对每个小文件,采用trie树/hash_map等统计每个文件中出现的词以及相应的频率。
  3. 堆/归并排序
    取出出现频率最大的100个词(可以用含100个结点的最小堆)后,再把100个词及相应的频率存入文件,这样又得到了5000个文件。最后就是把这5000个文件进行归并(类似于归并排序)的过程了。
4. 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10

解法一
如果同一个数据元素只出现在某一台机器中,那么可以采取以下步骤统计出现次数TOP10的数据元素:

  1. 堆排序
    在每台电脑上求出TOP 10,可以采用包含10个元素的堆完成
  2. 组合合并
    求出每台电脑上的TOP 10后,然后把这100台电脑上的TOP 10组合起来,共1000个数据,再利用上面类似的方法求出TOP 10就可以了。

解法二
但如果同一个元素重复出现在不同的电脑中
这个时候,你可以有两种方法:
1. 遍历一遍所有数据,重新hash取摸,如此使得同一个元素只出现在单独的一台电脑中,然后采用上面所说的方法,统计每台电脑中各个元素的出现次数找出TOP 10,继而组合100台电脑上的TOP 10,找出最终的TOP 10。
2. 或者,暴力求解:直接统计统计每台电脑中各个元素的出现次数,然后把同一个元素在不同机器中的出现次数相加,最终从所有数据中找出TOP 10。

5. 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序

解法一:

  1. hash映射
    顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为a0,a1,…a9)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。
  2. hash_map统计
    找一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。注:hash_map(query, query_count)是用来统计每个query的出现次数,不是存储他们的值,出现一次,则count+1。
  3. 堆/快排/归并
    利用快速/堆/归并排序按照出现次数进行排序,将排序好的query和对应的query_cout输出到文件中,这样得到了10个排好序的文件(记为b0,b1,…b10)。最后,对这10个文件进行归并排序(内排序与外排序相结合)。

解法二
一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。这样,我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。

解法三
与解法1类似,但在做完hash,分成多个文件后,可以交给多个文件来处理,采用分布式的架构来处理(比如MapReduce),最后再进行合并。

6. 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?

解法
可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。

  1. 分而治之/hash映射
    遍历文件a,对每个URL求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件中,记为a0,a1,a2,…a999。这样每个小文件大约300M。
    遍历文件b,采取和a相同的方式,将url分别存储到1000个小文件中,记为b0,b1,…,b999。然后我们只要求出1000对小文件中相同的url即可。
  2. hash_set统计
    求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。
7. 100万个数中找出最大的100个数

解法一:采用局部淘汰法。选取前100个元素,并排序,记为序列L。然后一次扫描剩余的元素x,与排好序的100个元素中最小的元素比,如果比这个最小的要大,那么把这个最小的元素删除,并把x利用插入排序的思想,插入到序列L中。依次循环,知道扫描了所有的元素。复杂度为O(100万*100)。

解法二:采用快速排序的思想,每次分割之后只考虑比主元大的一部分,直到比主元大的一部分比100多的时候,采用传统排序算法排序,取前100个。复杂度为O(100万*100)。

解法三:在前面的题中,我们已经提到了,用一个含100个元素的最小堆完成。复杂度为O(100万*lg100)。

猜你喜欢

转载自blog.csdn.net/u010521366/article/details/89041568