【算法】算法进阶训练(大数据专题)

大数据与位运算专题


题目一:40亿个非负整数中找到没出现的数

题目:32位无符号整数的范围是0 - 4294967295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然有没出现过的数。可以使用最多1GB的内存,怎么找到所有没出现过的数?

解答:对于原问题,如果使用哈希表来保存出现过的数,那么最坏情况下是40亿个数都不相同,那么哈希表则需要保存40亿条数据,一个32位整数需要4B,那么40亿*4B = 160亿个字节,一般大概10亿个字节的数据需要1G的空间,那么大概需要16G的空间,这不符合要求。

哈希表需要占用很多空间,因此可以使用bit map的方式来表示数出现的情况。具体地,申请一个bit数组,数组大小为4294967295,大概为40亿bit,40亿/8 = 5亿字节,那么需要0.5G空间, bit数组的每个位置有两种状态0和1,那么怎么使用这个bit数组呢?数组的长度刚好满足我们整数的个数范围,那么数组的每个下标值对应4294967295中的一个数,逐个遍历40亿个无符号数,例如,遇到100,则bitArray[100] = 1,遇到9999,则bitArray[9999] = 1,遍历完所有的数,将数组相应位置变为1。

遍历完成后,再依次遍历bitArray,哪个位置上的值没有被设置为1,哪个数就不在40亿个数中。例如,发现bitArray[8001]==0,那么8001就是没有出现过的数,遍历完bitArray之后,所有没有出现的数就找出来了。

进阶问题:如果将上题的内存空间限制改为10MB,但是只用找到一个没出现过的数即可。

解答:首先,将0~4294967295的区间分成64份,每个区间是67108864个数,为了定位更加准确一些,我们先开辟一个大小为64的整型数组intArray,将40亿个数进行区间划分,第0区间(0-67108863)、第一区间(67108864-134217728)、第i区间(67108864*i-67108864*(i+1)-1),......,第63区间(4227858432 - 4294967259)。intArray分别记录每个区间出现的数的个数。遍历40亿个数,根据当前数是多少来决定哪一个区间上的计数增加。例如如果当前数是3422552090,3422552090/67108864=51,所以第51区间上的计数增加:intArray[51]++。遍历完40亿个数之后,遍历intArray,必然会有一个区间上的计数少于67108864,表示第i区间上至少有一个数没出现过。利用这一点可以快速找出一个没有出现过的数。

假如找到第37区间上的计数小于67108864,以下为第二次遍历的过程:

1、申请长度为67108864的bit数组bitArray[67108864],占用内存空间大约8MB;

2、再遍历40亿个数,这次只需要找出位于区间 k 的所有数,如果 n / 67108864 == k,那么n属于k区间;

3、在第2步的找出的 n,将bitArray[n - 67108864*k]的值置为 1,只做第k区间上数的bitArray映射;

4、遍历完成之后,在bitArray上必然存在状态是0的位置,假设第 i 个位置没被设置成1,那么67108864*k + i这个数就是有个没有出现过的数。

总结一下进阶的解法:

1. 根据10MB的内存限制,确定统计区间的大小,就是第二次遍历时的bitArray大小。

2. 利用区间计数的方式,找到那个计数不足的区间,这个区间上肯定有没出现的数。

3. 对这个区间上的数做bit map映射,再遍历bit map,找到一个没出现的数即可。

题目二:找到100亿个URL中重复的URL以及搜索词汇的topK问题

题目:有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL。

补充题目:某搜索公司一天的用户搜索词汇是海量的(百亿数据量),请设计一种求出每天最热top100词汇的可行办法。

解题前首先要明确,资源上的限制,包括内存、计算时间等要求。

解答:原问题的解法使用解决大数据问题的一种常规方法:把大文件通过哈希函数分配到机器,或者通过哈希函数把大文件拆成小文件。一致进行这种划分,直到划分的结果满足资源限制的要求。具体过程如下:

①将100亿的大文件通过哈希函数分配到100台机器上,哈希函数的性质决定了同一条URL不可能划分给不同的机器。

②然后每一台机器分别统计给自己的URL中是否有重复的URL(或者在单机上将大文件通过哈希函数拆成1000个小文件,对每一个小文件再利用哈希表遍历),找出重复的URL

③还可以在分给机器或拆完文件之后进行排序找重复的URL,排序过后看是否有重复的URL出现。

对于补充问题,还是用哈希分流的思想来处理,把包含百亿数据的词汇文件分流到不同的机器上。具体过程如下:

①哈希分流。首先把包含百亿数据量的词汇文件分流到不同机器上;如果每一台机器上分到的数据量依然很大,再用哈希函数把每台机器的分流文件拆成更小文件处理;

②处理每一个小文件时,通过哈希表做一个词频统计,哈希表记录完成后,再遍历哈希表,过程中用大小为100的小根堆来选出每一个小文件的Top100(未排序)

③将小文件的Top100的小根堆按照词频排序,把每个小文件进行排序后的Top100进行排序或者利用小根堆,选出每台机器上的Top100;

④不同机器之间的Top100再进行外排序或者继续利用小根堆,最终求出整个百亿数据量中的Top 100。

题目三:40亿个非负整数中找到出现两次的数和所有数的中位数

题目:32位无符号整数的范围为0-4294967295,现在有40亿个无符号整数,可以使用最多1GB内存,找出所有出现了两次的数。

补充题目:可以使用最多100MB的内存,怎么找到这40亿个数的中位数?

解答:对于原问题,可以用bit map的方式来表示数出现的情况。具体地,申请一个长度为4294967295*2的bit类型的数组bitArray,其中每2位代表一个数,1B占用8个bit,所以长度为4294967295*2的bit类型的数组占用1G空间。这两位中00代表出现0次,01出现1次,10出现2次,11出现3次。如果遍历过程中发现已经是11了则继续遍历下一个即可。之后再次遍历所有整数找出比特位10的数。

对于补充问题:采用分区间的方式处理,长度为2MB的无符号整型数组占用的空间为8MB,所以将区间的数量设定为4294967295/2M,向上取整为2148个区间。第0个区间为0~2M-1,第1个区间为2M~4M-1,...,第i个区间为2M*i~2M*(i+1)-1;申请一个长度为2148的无符号整型数组arr[0...2147],arr[i]表示第i区间有多少个数。arr必然小于10MB。然后遍历40亿个数,如果遍历到当前数为num,先看num落在哪个区间上(num/2M),然后将对应的进行arr[num/2M]++操作。这样遍历下来,就得到了每一个区间的数出现的状况,通过累加每个区间的出现次数,就可以找到40亿个数的中位数到底落在哪个区间上。

接下来再次遍历所有数,但是只关注落到第k区间数的计数值,然后就可以找到中位数是哪一个了。具体的,申请一个长度为2MB的无符号整型数组countArr[0...2M-1],占用空间8MB。遍历40亿个数,只关心处在第k区间的数几位numi,然后将countArr[numi-K*2M]++,也就是只对第k区间的数做频率统计。遍历完之后,就得到第k区间的词频统计结果countArr,最后只在第k区间上找到第0.002亿个数即可。

题目四:只用2GB内存在20亿个整数中找到出现次数最多的数

题目:有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。

要求:内存限制为2GB

解答:在很多整数中找到出现次数最多的数,通常的做法是使用哈希表对每一个数做词频统计,哈希表的key是某一个整数,value是这个数出现的次数。就本题来说,一共有20亿个数,哪怕只是一个数出现了20亿次,用32位的整数也可以表示其出现的次数而不会产生溢出,,所以哈希表的key需要占用4B,value也是4B。那么哈希表的一条记录(key,value)需要占用8B,当哈希表记录数为2亿个时,需要至少1.6GB的内存。

但是如果20亿个数中不同的数超过2亿种,最极端的情况时20亿个数都不同,那么在哈希表中可能需要产生20亿条记录,这样内存会不够用,所以一次性用哈希表统计20亿个数的办法是有很大风险的。

解决办法是把包含20亿个数的大文件用哈希函数分成16个小文件(确定分成16个文件,就是根据内存限制2GB的条件来确定的),根据哈希函数的性质,同一种数不可能被哈希到不同的小文件上,同时每一个小文件中不同的数一定不会大于2亿中,假设哈希函数足够好。然后对每一个小文件用哈希表来统计其中每种数出现的次数,这样我们就得到了16个小文件中各自出现次数最多的数,还有各自的次数统计。接下来只要选出这16个小文件各自的第一名中谁出现的次数最多即可。

把一个大的集合通过哈希函数分配到多台机器中,或者分配到多个文件里,这种技巧是处理大数据面试题时最常用的技巧之一。

如果给的这40 亿个数中数值都是一样的,那么哈希表中,某个key的value存放的数值就会是 40 亿,然而 int 的最大数值是21亿左右,那么就会出现溢出,该怎么办?一种方法是(把 int 改为 long 会占用更多的内存)可以把value初始值赋值为 负21亿,这样,如果 value 的数值是 21 亿的话,就代表某个 key 出现了 42 亿次了。另一种方案是可以一边遍历一遍判断,如果在统计的过程中,发现某个key出现的次数超过了 40 亿次,那么,就不可能再有另外一个 key 出现的次数比它多了,那我直接把这个key返回就可以了。

知识点:布隆过滤器

题目:不安全的网页黑名单包含100亿个黑名单网页,每个网页最多占用64B。现在想要实现一个网页过滤器系统,可根据URL判断该网页是否在黑名单上。

要求:

  1. 该系统允许有一定的判断失误率(不高于万分之一)
  2. 使用的额外空间不超过30GB

解答:当遇到网页黑名单系统、垃圾邮件过滤系统、爬虫的网站判重系统等题目时,又有一定程度的失误率,并且对空间要求比较严格,那么就需要考虑布隆过滤器。一个布隆过滤器精确地代表一个集合,并可以精确判断一个元素是否在集合中。注意,只是精确代表和精确判断,到底有多精确,则完全取决于具体的设计,做到完全正确是不可能的。

首先介绍哈希函数的概念:

  1. 典型的哈希函数都有无限的输入值域
  2. 当哈希函数传入相同的输入值时,返回值一样
  3. 给哈希函数传入不同的输入值时,返回值可能一样也可能不一样,因为输出域统一是S,所以会有不同的输入值对应在S中的一个元素上
  4. 很多不同的输入值所得到的返回值会均匀分布在S上(决定哈希函数的优略)

接下来介绍什么是布隆过滤器。假设有一个长度为m的bit类型的数组,即数组中的每一个位置只占一个bit,每一个bit只有0和1两种状态,如下图所示:

在这里插入图片描述

再假设一共有k个哈希函数,这些哈希函数的输出域S都大于或等于m,并且这些哈希函数都足够优秀,彼此之间也完全独立。那么对于同一个输入对象(假设是一个字符串记为URL),经过k个哈希函数算出来的结果也是独立的。可能相同,也可能不同,但彼此独立。然后对算出来的每一个结果都对m取余(%m),然后在bit array上把相应的位置设为1。

这里把bit类型的数组称为bitMap。至此,一个输入对象的影响就结束了,也就是bitMap的一些位置会被涂黑,如果遇到黑的位置则继续为黑即可。处理完所有的对象后,bitMap中有相当多的位置被涂黑。至此,一个布隆过滤器就完成了。

如何检查对象是否应该被过滤呢?假设一个对象为a,想要判断它是否应该被过滤,具体过程是把a通过k个哈希函数算出k个值,然后把k个值取余(%m),就得到了在[0...m-1]范围上的k个值。接下来在bitMap上看这k个位置是不是都为黑。如果有一个不为黑,说明a一定不在这个黑名单集合里,即不应该被过滤。如果这k个位置都为黑,则说明a在黑名单集合里,则应该被过滤。这里存在一定失误率,具体和bitMap大小有关。如果bitMap的大小m相比于输入对象的个数n过小,失误率会变大。

对于本题来说,黑名单样本中的样本个数为100亿,记为n,失误率不能超过0.01%,记为p,每个样本的大小为64B,布隆过滤器的大小m由以下公式确定:

m=-{\frac{n*ln p}{(ln 2)^2}}

根据公式计算出m=19.19n,向上取整为20n,急需要2000亿个bit,也就是25GB。

哈希函数的个数由以下公式决定:

k=ln2\times \frac{m}{n}=0.7\times \frac{m}{n}

计算出哈希函数的个数为14个。然后用25GB的bitMap再单独实现14个哈希函数,即可生成上面描述的布隆过滤器。

因为在确定布隆过滤器大小的过程中选择了向上取整,所以用如下公式确定布隆过滤器的真实失误率为:

(1-e^{-\frac{nk}{m}})^k

算出的真实失误率约为0.006%。这比0.01%的要求更低,而且哈希函数本身不占用什么空间,所以使用的空间就是bitMap的大小(即25GB)。

知识点:一致性哈希算法的基本原理

题目:工程师常使用服务器集群来设计和实现数据缓存,以下是常见的策略:

1.无论是添加、查询还是删除数据,都先将数据的id通过哈希函数转换成一个哈希值,记为key。

2.如果目前机器有N 台,则计算key%N的值,这个值就是该数据所属的机器编号,无论是添加、删除还是查询操作,都只在这台机器上进行。

请分析这种缓存策略可能带来的问题,并提出改进的方案。

解答:题目中描述的缓存策略的潜在问题是:如果增加或删除机器(N变化)时,代价会很高,因为所有的数据都不得不根据id重新计算一遍哈希值,并将哈希值对新机器数进行取模操作,然后进行大规模的数据迁移。

为了解决这些问题,引入一致性哈希算法。假设数据的id通过哈希函数转换成的哈希值范围是2^32,也就是(0~2^32-1)的数字空间中。那么可以将这些数字头尾相连,想象成一个闭合的环形。那么一个数据id在计算出哈希之后认为对应到环中的一个位置上,如下图所示:

哈希环

接下来,想象有三台机器也处在这样一个环境中,这三台机器在环中的位置根据机器id计算出的哈希值来决定。那么一条数据如何确定归属哪台机器呢?首先把该数据的id用哈希值算出哈希值,并映射到环中的相应位置,然后顺时针找寻离这个位置最近的机器,那台机器就是该数据的归属。如下图所示

一条数据如何确定归属哪台机器呢

在上图中,data1根据其id计算出的哈希值为key1,顺时针的第一台机器时machine2,所以data1归属于machine2。同理,data2归属于machine3,data3和data4归属于machine1。

1.节点删除

以上面的分布为例,如果machine2出现故障被删除了,那么按照顺时针迁移的方法,Hash值属于图中红色片段的所有数据将会被迁移到machine3中,这样仅仅是红色的一段映射位置发生了变化,其它的对象没有任何的改动。如下图:

节点删除

2. 节点添加

如果往集群中添加一个新的节点Node4,通过对应的哈希算法得到Key4,并映射到环中,如下图:

节点添加

按照顺时针迁移的规则,数据Hash值处于红色段的数据被迁移到了Node4中,其它对象还保持这原有的存储位置。通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,数据的迁移时间达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。

如何解决数据倾斜问题?

如果机器较少,即处理节点较少,很可能造成机器在整个环上的分布不均匀,从而导致机器之间的负载不均衡。为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一台机器通过不同的哈希函数计算出多个哈希值,对多个位置都放置一个服务节点,称为虚拟节点。具体做法可以在机器ip或主机名的后面增加编号或端口号来实现。根据哈希函数的性质,增加虚拟节点,也就是的环中的服务节点变多,平衡性自然就会变好一些,而此时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。基于一致性哈希的原理有多种具体的实现,包括Chord算法、KAD算法.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值