海量数据处理 分而治之/hash映射和堆排序

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

什么是hash映射?

简单来说,就是为了便于计算机在有限的内存中处理big数据,我们通过一种映射散列的方式让数据均匀分布在对应的内存位置(如大数据通过取余的方式映射成小数存放在内存中,或大文件映射成多个小文件),而这个映射散列方式便是我们通常所说的hash函数,设计的好的hash函数能让数据均匀分布而减少冲突。

什么是TOP K?

有N(N>>10000)个整数,求出其中的前K个最大的数。(称作Top k或者Top 10)

  问题分析:由于(1)输入的大量数据;(2)只要前K个,对整个输入数据的保存和排序是相当的不可取的。

        可以利用数据结构的最小堆来处理该问题。

        最小堆对于每个非叶子节点的数值,一定不大于孩子节点的数值。这样可用含有K个节点的最小堆来保存K个目前的最大值(当然根节点是其中的最小数值)。

      每次有数据输入的时候可以先与根节点比较。若不大于根节点,则舍弃;否则用新数值替换根节点数值。并进行最小堆的调整。

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

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

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

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

1.分而治之/hash映射 
首先把这一天访问百度日志的所有IP提取出来,然后逐个写入到一个大文件中,接着采用映射的方法,比如%1000,把整个大文件映射为1000个小文件。

2.hash_map统计 
当大文件转化成了小文件,那么我们便可以采用hash_map(ip, value)来分别对1000个小文件中的IP进行频率统计,再找出每个小文件中出现频率最大的IP。

遍历map,相同value+1,不同添加key。

if(map.containsKey(key)){ map.put(key,map.get(key) + 1); } else { map.put(key,1); }

3.堆/快速排序 
统计出1000个频率最大的IP后,依据各自频率的大小进行排序(可采取堆排序),找出那个频率最大的IP,即为所求。

例题2:如何从100万个数中找出最大的前100个数:
1.堆排序
先取出前100个数,维护一个100个数的最小堆,遍历一遍剩余的元素,在此过程中维护堆就可以了。具体步骤如下: 
step1:取前m个元素(例如m=100),建立一个小顶堆。保持一个小顶堆得性质的步骤,运行时间为O(lgm);建立一个小顶堆运行时间为m*O(lgm)=O(m lgm);       
step2:顺序读取后续元素,直到结束。每次读取一个元素,如果该元素比堆顶元素小,直接丢弃 
如果大于堆顶元素,则用该元素替换堆顶元素,然后保持最小堆性质。最坏情况是每次都需要替换掉堆顶的最小元素,因此需要维护堆的代价为(N-m)*O(lgm); 
最后这个堆中的元素就是前最大的100个。时间复杂度为O(N lgm)。
2.分块查找 
先把100w个数分成100份,每份1w个数。先分别找出每1w个数里面的最大的数,然后比较。找出100个最大的数中的最大的数和最小的数,取最大数的这组的第二大的数,与最小的数比较,如果第二个数比最小的那个大则替换掉最小的,否则取第二大数的这组的第二大的数和最小的比较,依次类推。

如何从一个只有两个是重复的大数组中找出那个重复的数:
创建一个hashset数组,向里面添加数组的每一个数据,当出现插不进去的时候那个数组下标就是重复的数据。


 例题3:假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

hash统计:先对这批海量数据预处理(维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内用Hash表完成了统计;

堆排序:第二步、借助堆这个数据结构,找出Top K,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比所以,我们最终的时间复杂度是:O(N) + N'*O(logK),(N为1000万,N’为300万)。

例题4:有一个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个文件进行归并(类似于归并排序)的过程了。


一个非常大的数组,从中找连续的n个数,求他们的和。
创建一个新的数组,新数组的长度是原数组长度+1,新数组的下标0为原数组下标0之前的和,下标1为原数组下标1之前的和,依次类推。找连续的n个数,如从第a个数(不是第a个下标)到第b个数,就是求新数组下标为b减下标为(a-1)的值

猜你喜欢

转载自blog.csdn.net/qq_39404258/article/details/82115419