(4.1)亿万级数据处理(分而治之/Hash映射 + HashMap统计 + 堆/快速/归并排序)

秘技一:分而治之/Hash映射 + HashMap统计 + 堆/快速/归并排序

Hash

任意长度的输入(又叫做预映射, pre-image)通过散列算法变换成固定长度的输出(散列值)

这种转换是一种压缩映射,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而可能从散列值来唯一的确定输入值。简单说就是一种将任意长度的消息压缩到某一固定长度的函数。

hash映射:

简单来说,为了便于内存中处理大数据,通过一种映射散列的方式让数据均匀分布在对应的内存位置(大数据取余映射成小树存放在内存中,大文件映射成小文件),这个映射散列方式就是hash函数,好的hash函数能让数据均匀分布而减少冲突。尽管数据映射到了另外一些不同的位置,但数据还是原来的数据,只是代替和表示这些原始数据的形式发生了变化而已

主要用于安全加密算法不同长度信息转成杂乱128位编码(Hash值).hash找到数据内容数据存放地址之间映射关系

数组特点:寻址容易,插入和删除困难

链表特点:寻址困难,插入和删除容易。

综合两者的特性,做寻址,插入删除容易的数据结构:哈希表,拉链法,“链表的数组”

左边很明显是个数组,指针指向链表头(可能空),也可能元素很多。根据元素特征分配到不同链表,根据特征,找到链表,再从链表中找出元素

元素特征转变为数组下标的方法就是散列法

1.除法散列法(上图使用)

公式:index = value % 16    取模是通过除法得到

2.平方散列法

求index是非常频繁的操作,而乘法运算要比除法来得省时,所以我们考虑把除法换成乘法和一个位移操作

公式:index = (value * value) >> 28

数值分配比较均匀,但上图的各个元素的值算出来的index都是0——非常失败。value很大,value * value会溢出,但这个乘法不关心溢出,我们不是为了获取相乘结果,而是index

3.斐波那契(Fibonacci)散列法

找出一个理想的乘数,不用value,根据黄金分割法则

1,对于16位整数而言,这个乘数是40503

2,对于32位整数而言,这个乘数是2654435769

3,对于64位整数而言,这个乘数是11400714819323198485

黄金分割法则的最经典表达式:斐波那契数列

对我们常见的32位整数而言,公式:index = (value * 2654435769) >> 28

上面的图就变成这样了,比取模散列法好很多:

适用快速查找,删除的基本数据结构,需要总数据量可放入内存

基本原理及要点

(1)Hash函数选择,针对字符串,整数,排列,具体相应的hash方法

(2)碰撞处理:1.哈希法,亦拉链法;2.开地址法closed hashing,opened addressing。

(3)扩展

d-left hashing:d是多个的意思

2-left hashing:将一个哈希表分成长度相等两半,T1和T2,分别配备一个哈希函数h1和h2存储一个新key时,两个哈希函数同时计算,得出两个地址h1[key]和h2[key]。这时需要检查h1、2[key]位置,哪一个存储的(有碰撞的)key多,将新key存储负载少的位置。如果一样多,存储在左边的T1子表中,2-left也由此而来。查找一个key,必须进行两次hash,同时查找两个位置

十道海量数据处理面试题:

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

(1)思路:无非分而治之/hash映射 + hash统计 + 堆/快速/归并排序

hash映射:数据太大,内存受限,把大文件化成(取模映射)文件

HashMap统计:常规的HashMap(ip,value)进行频率统计

堆/快速排序:堆排序,得到次数最多的IP

(2)解题步骤:

这一天,访问百度日志中的IP取出来,逐个写入一个大文件中。最多2^32个IP(IP是32位)。映射的方法,比如%1000,映射为1000个小文件

再找出每个小文件中出现频率最大的IP(可以采用HashMap对那1000个文件中的所有IP进行频率统计,然后依次找出各个文件中频率最大的那个IP)及相应的频率。

然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。

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

特殊的二叉树:

(1)每个节点的值都大于(或者都小于,即最小堆)其子节点的值

(2)树完全平衡的,并且最后一层的树叶都在最左边

最大堆:

数组表示堆

二叉堆:完全二叉树,左右节点(如果有的话)比根节点大,上图就是

入队:入队一个单元,键值为2,那只需在数组末尾加入这个元素,往上挪直到挪不动,复杂度为Ο(logn)的操作。


出队:一定出第一个元素,这么来第一个元素以前的位置就成了空位,数组最后一个元素插入这个空位,往上挪。复杂度也是Ο(logn)


适用范围:海量数据前n大,并且n比较小,堆可以放入内存

基本原理及要点:

最大堆求前n小,最小堆求前n大。方法,比如求前n小,我们比较当前元素与最大堆里的最大元素,如果它小于最大元素,则应该替换那个最大元 素。这样最后得到的n个元素就是最小的n个。适合大数据量,求前n小,n的大小比较小的情况,这样可以扫描一遍即可得到所有的前n元素,效率很高。

扩展:双堆,一个最大堆与一个最小堆结合,可以用来维护中位数。

3.寻找热门查询,300万个查询字符串中统计最热门的10个查询,内存1G

查询串1-255字节1000万个记录(除重后300万个。重复度越高,查询它越热门)

一亿个IP求Top 10,分1000,小堆依次处理Top10,规模比较小,能一次性装入内存呢?

虽一千万个Query,其实只有300万的Query(每个 255字节),放进内存中去(300万个字符串假设没有重复,都是最大长度,那么最多占用内存300 0000 *1K/4=750000kb=750mb=0.75G),放弃分而治之直接hash统计,然后排序。HashMap + 堆

HashMap统计(O(N))

维护HashMap(Query,Value出现次数),每次读取一个Query,不在HashMap中,加入该串Value设1;在HashMap,计数加一

堆排序

找出Top K,N*logK,

维护K(该题目中是10)大小的小根堆,遍历300万的Query,分别和根元素进行对比。

最终的时间复杂度是O(N) + N' * O(logK),(N为1000万,N’为300万)。

堆排序思路

维护k个元素的最小堆,即用容量为k的最小堆存储最先遍历到的k个数,并假设它们即是最大的k个数,建堆O(k),调整堆O(logk)后,有

k1>k2>...kmin(kmin设为小顶堆中最小元素)

继续遍历数列,每次遍历一个元素x,与堆顶元素比较,若x>kmin,则更新堆(x入堆,用时logk),否则不更新堆。这样下来,总费时O(k*logk+(n-k)*logk)=O(n*logk)

优点:堆中,查找等各项操作时间复杂度均为logk

也可以采用trie树,关键字域存该查询串出现的次数,没有出现为0

最后用10个元素的最小堆来对出现频率进行排序。

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

(1)分而治之/hash映射

顺序读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为x0,x1,...x4999)中。这样每个文件大概是200k(=1000*1000/5000)

如果其中的有的文件超过了1M,类似的方法继续下分,直到小文件都不超过1M

(2)HashMap统计:对每个小文件,采用trie树/HashMap等统计每个文件中出现的词以及相应的频率

(3)堆/归并排

取出频率最大的100个词(含100个结点最小堆),存入得到了5000个文件,归并

ps:不能每个里面取最高的,有可能A文件中:9,8 ;B:1,2

4.海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10,如果每个数据元素只出现一次,而且只出现在某一台机器中,那么可以采取以下步骤统计出现次数TOP10的数据元素:

堆排序

(1)每台电脑TOP10,可以采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆,比如求TOP10大,我们首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大)。

(2)把这100台电脑上的TOP10组合起来,共1000个数据,上面类似方法。

二、如果同一个元素重复出现在不同的电脑中呢

方法1:遍历所有,重新hash取模,同一个元素只出现在单独的一台电脑中,按上面办法

方法2:暴力求解:直接统计每台电脑中各个元素的出现次数,然后把同一个元素在不同机器中的出现次数相加,最终从所有数据中找出TOP10

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

方案1

遍历所有,重新hash取模(新文件大约也1G,假设hash函数较好)

内存2G左右机器,依次用HashMap(query, query_count)统计每个query频度,出现一次count+1

对这10个文件进行归并排序(内/外排相结合)

方案2

一次性加入到内存。用trie树/HashMap等统计每个query出现次数,做快速/堆/归并排序

方案3

做完hash,分成多个文件后,交给多个文件来处理,采用分布式的架构来处理(比如MapReduce),最后再进行合并

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

5G×64=320G(50*10000*10000k/1000kb/1000mb/1000gb=5,大于内存限制。

遍历文件a,对每个url求取

然后根据所取得的值将url分别存储到1000个小文件

(漏个a1)中

小文件大约300M,b同上。所有可能相同的url都在对应的小文件

不对应的小文件不可能有相同的url,求出1000对小文件中相同的url即可

HashSet统计:每对小文件中相同的url时,其中一个小文件的url存储到HashSet

然后遍历另一个小文件的url,是否HashSet中,是,就是共同的url,存到文件

分而治之/hash映射 + hash统计 + 堆/快速/归并排序

9.一个文本文件,约一万行,每行一个词,统计出其中最频繁的10个词,给出思想及时间复杂度分析

方案1

文件较大,hash取模,HashMap统计。归并

方案2

hash取模,trie树统计每个词出现的次数,时间复杂度O(n*le)(le:单词平均长度),最终同样找出出现最频繁的前10个词(可用堆来实现),时间复杂度是O(n*lg10)。

10. 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?

方案1:这题用trie树比较合适,hash_map也行。

方案2:1000w的数据规模插入操作完全不现实,以前试过在stl下100w元素插入set中已经慢得不能忍受,觉得基于hash的实现不会比红黑树好太多,使用vector+sort+unique都要可行许多,建议还是先hash成小文件分开处理再综合。

11.100w个数中找出最大的100个数

方案1:局部淘汰法

取前100个元素,并排序,记为序列L

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

方案2

快排思想,分割之后只考虑比轴大的部分,知道比轴大的一部分在比100多的时候,传统排序,取前100个。复杂度为O(100w*100)

方案3(最优)

100个元素的最小堆完成。复杂度为O(100w*lg100)。ps:lg100=2,100=10的2次方

你可能感兴趣的:((4.1)亿万级数据处理(分而治之/Hash映射 + HashMap统计 + 堆/快速/归并排序))