常见问题:
①Top K问题:分治+Trie树/Hash_map+小顶堆。采用Hash(x)%M将原文件分割成小文件,如果小文件太大则继续Hash分割,直至可以放入内存。
②重复问题:BitMap位图 或 Bloom Filter布隆过滤器 或 Hash_set集合。每个元素对应一个bit处理。
③排序问题:外排序 或 BitMap位图。分割文件+文件内排序+文件之间归并。
Top K问题:
1. 有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
①分治:顺序读文件,对每个词x取Hash(x)%5000,按照该值存到5000个小文件中。每个文件是200k左右。如果有文件超过了1M则继续分割。
②Trie树/Hash_map:字符串用Trie树最好。对每个小文件,统计其中出现的词频。
③小顶堆:用容量为100的小顶堆,以频率为元素插入,取每个文件现频率最大的100个词,把这100个词及相应的频率存入文件。
④归并:将得到的5000个文件进行归并,取前100个词。
2. 海量日志数据,提取出某日访问百度次数最多的那个IP。
①分治:IP是32位,共有232个IP。访问该日的日志,将IP取出来,采用Hash,比如模1000,把所有IP存入1000个小文件。
②Hash_map:统计每个小文件中出现频率最大的IP,记录其频率。
③小顶堆:这里用一个变量即可。在这1000个小文件各自最大频率的IP中,找出频率最大的IP。
3. 海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。
分析:虽然数据已经是分布的,但是如果直接求各自的Top10然后合并的话,可能忽略一种情况,即有一个数据在每台机器的频率都是第11,但是总数可能属于Top10。所以应该先把100台机器中相同的数据整合到相同的机器,然后再求各自的Top10并合并。
①分治:顺序读每台机器上的数据,按照Hash(x)%100重新分布到100台机器内。单台机器内的文件如果太大,可以继续Hash分割成小文件。
②Hash_map:统计每台机器上数据的频率。
③小顶堆:采用容量为10的小顶堆,统计每台机器上的Top10。然后把这100台机器上的TOP10组合起来,共1000个数据,再用小顶堆求出TOP10。
4. 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。
①分治:一万行不算多,不用分割文件。
②Trie树:统计每个词出现的次数,时间复杂度是O(n*le) (le表示单词的平准长度)。
③小顶堆:容量为10的小顶堆,找出词频最多的前10个词,时间复杂度是O(n*lg10) (lg10表示堆的高度)。
总的时间复杂度是 O(n*le)与O(n*lg10)中较大的那一个。
5. 一个文本文件,找出前10个经常出现的词,但这次文件比较长,说是上亿行或十亿行,总之无法一次读入内存,问最优解。
比上一题多一次分割。
①分治:顺序读文件,将文件Hash分割成小文件,求小文件里的词频。
②③同上。
6. 100w个数中找出最大的100个数。
方法1:用容量为100的小顶堆查找。复杂度为O(100w * lg100)。
方法2:采用快速排序的思想,每次分割之后只考虑比标兵值大的那一部分,直到大的部分在比100多且不能分割的时候,采用传统排序算法排序,取前100个。复杂度为O(100w*100)。
方法3:局部淘汰法。取前100个元素并排序,然后依次扫描剩余的元素,插入到排好序的序列中,并淘汰最小值。复杂度为O(100w * lg100) (lg100为二分查找的复杂度)。
重复问题:
1. 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
分析:每个文件的大小约为50G×64=320G,远远大于内存大小。考虑采取分而治之的方法。
方法1:
①分治:遍历文件a,对每个url求Hash%1000,根据值将url分别存储到1000个小文件中,每个小文件约为300M。文件b采用同样策略。上述两组小文件中,只有相同编号的小文件才可能有相同元素。
②Hash_set:读取a组中一个小文件的url存储到hash_set中,然后遍历b组中相同编号小文件的每个url,查看是否在刚才构建的hash_set中。如果存在,则存到输出文件里。
方法2:
如果允许有一定的错误率,可以使用Bloom filter,4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit,然后挨个读取另外一个文件的url,检查是否在Bloom filter中。如果是,那么该url应该是共同的url(注意会有一定的错误率)。
2. 在2.5亿个整数中找出不重复的整数,内存不足以容纳这2.5亿个整数。
分析:2.5亿个整数大概是954MB,也不是很大。当然可以更节省内存。
方法1:
采用2-Bitmap,每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义。共需内存60MB左右。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完后,查看Bitmap,把对应位是01的整数输出。
方案2:
分治法,Hash分割成小文件处理。利用Hash_set,在小文件中找出不重复的整数,再进行归并。
3. 一个文件包含40亿个整数,找出不包含的一个整数。分别用1GB内存和10MB内存处理。
1GB内存:
①Bitmap:对于32位的整数,共有232个,每个数对应一个bit,共需0.5GB内存。遍历文件,将每个数对应的bit位置1。最后查找0bit位即可。
10MB内存: 10MB = 8 × 107bit
①分治:将所有整数分段,每1M个数对应一个小文件,共4000个小文件。
②Hash_set:对每个小文件,遍历并加入Hash_set,最后如果set的size小于1M,则有不存在的数。利用Bitmap查找该数。
4. 有10亿个URL,每个URL对应一个非常大的网页,怎样检测重复的网页?
分析:不同的URL可能对应相同的网页,所以要对网页求Hash。1G个URL+哈希值,总量为几十G,单机内存无法处理。
①分治:根据Hash%1000,将URL和网页的哈希值分割到1000个小文件中,重复的网页必定在同一个小文件中。
②Hash_set:顺序读取每个文件,将Hash值加入集合,如果已存在则为重复网页。
排序问题:
1. 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求按照query的频度排序。
方法1:
①分治:顺序读10个文件,按照Hash(query)%10的结果将query写入到另外10个文件。新生成的每个文件大小为1G左右(假设hash函数是随机的)。
②Hash_map:找一台内存为2G左右的机器,用Hash_map(query, query_count)来统计次数。
③内排序:利用快速/堆/归并排序,按照次数进行排序。将排序好的query和对应的query_count输出到文件中,得到10个排好序的文件。
④多路归并:将这10个文件进行归并排序。
方案2:
一般query的总量是有限的,只是重复的次数比较多。对于所有的query,一次性就可能加入到内存。这样就可以采用Trie树/Hash_map等直接统计每个query出现的次数,然后按次数做快速/堆/归并排序就可以了
方案3:
与方案1类似,在做完Hash分割后,将多个文件采用分布式的架构来处理(比如MapReduce),最后再进行合并。
2. 一共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到这N^2个数的中位数?
方法1: 32位的整数一共有232个
①分治:把0到232-1的整数划分成N段,每段包含232/N个整数。扫描每个机器上的N个数,把属于第一段的数放到第一个机器上,属于第二段的数放到第二个机器上,依此类推。 (如果有数据扎堆的现象,导致数据规模并未缩小,则继续分割)
②找中位数的机器:依次统计每个机器上数的个数并累加,直到找到第k个机器,加上其次数则累加值大于或等于N2/2,不加则累加值小于N2/2。
③找中位数:设累加值为x,那么中位数排在第k台机器所有数中第N2/2-x位。对这台机器的数排序,并找出第N2/2-x个数,即为所求的中位数。
复杂度是O(N2)。
方法2:
①内排序:先对每台机器上的数进行排序。
②多路归并:将这N台机器上的数归并起来得到最终的排序。找到第N2/2个数即是中位数。
复杂度是O(N2*lgN)。