以下基本来自左神的算法书
相关题目:
1.找到100亿url中最多出现的k个
分析:
这个只是知道一个大致的思路,先把思路写在这里。
在大规模数据处理中,经常会遇到的一类问题:在海量数据中找出出现频率最高的前k个数,或者从海量数据中找出最大的前k个数,这类问题通常被称为top K问题。例如,在搜索引擎中,统计搜索最热门的10个查询词;在歌曲库中统计下载最高的前10首歌等。
针对top K类问题,通常比较好的方案是分治+Trie树(字典树,单词查找树)/hash+小顶堆(堆顶的值小于等于左右两个节点的值,并且左右两个子树也是一个小顶堆),即先将数据集按照Hash方法分解成多个小数据集,然后使用Trie树或者Hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出现频率最高的前K个数,最后在所有top K中求出最终的top K。
原文来源:https://blog.csdn.net/zyq522376829/article/details/47686867
这里在补充一下另一种使用快速排序的思路:
TopK是希望求出arr[1,n]中最大的k个数,那如果找到了第k大的数,做一次partition,就可以找到Topk个元素。
在快速排序中第一次进行切分(partition),划分之后会返回这个元素在排序数组中的位置i:
i = partition(arr, 1, n);
如果i大于k,则说明arr[i]左边的元素都大于k,于是只递归arr[1, i-1]里第k大的元素即可;
如果i小于k,则说明说明第k大的元素在arr[i]的右边,于是只递归arr[i+1, n]里第k-i大的元素即可
2.只用20gb内存在20亿个整数中找到出现次数最多的数
要求:内存限制为2GB
分析:用哈希表进行存储的时候,key是4B,value是4B,这样当20亿的数据都不相同时(极端情况)下2GB的内存就会不够,因此態一次性用哈希表进行统计。解决方法是把20亿个数的大文件用哈希函数分成16个小文件,根据哈希函数的性质,同一种数不可能被哈希到彤彤的小文件上,同时每一个小文件中不同的数一定不会大于2一种,鸡舍哈希函数足够好,然后对每一个小文件用哈希表来统计其中每一种数出现的次数,这样我们就到得到了16个小文件中各自出现的次数最多的数,还有各自的统计,接下来只需要选出这16个小文件各自的第一名中出现次数最多的即可。
这个方法就是使用哈希算法将大文件划分为小文件处理。
原问题的解法:把大文件通过哈希函数分配到及其或者通过哈希函数把大文件拆分为小文件,一直进行这种划分知道划分满足要求。之后使用哈希表遍历找出重复的URL或者进行排序,查看是否有重复的URL出现。
普冲题目就是一个topk问题了,使用哈希函数分流,用哈希表做词频统计,使用堆结构和外排序手段进行处理。
总结:
实际上,最优的解决方案应该是最符合实际设计需求的方案,在时间应用中,可能有足够大的内存,那么直接将数据扔到内存中一次性处理即可,也可能机器有多个核,这样可以采用多线程处理整个数据集
下面针对不同的应用场景,分析了适合相应应用场景的解决方案。
(1)单机+单核+足够大内存
如果需要查找10亿个查询次(每个占8B)中出现频率最高的10个,考虑到每个查询词占8B,则10亿个查询次所需的内存大约是10^9 * 8B=8GB内存。如果有这么大内存,直接在内存中对查询次进行排序,顺序遍历找出10个出现频率最大的即可。这种方法简单快速,使用。然后,也可以先用HashMap求出每个词出现的频率,然后求出频率最大的10个词。
(2)单机+多核+足够大内存
这时可以直接在内存中使用Hash方法将数据划分成n个partition,每个partition交给一个线程处理,线程的处理逻辑同(1)类似,最后一个线程将结果归并。
该方法存在一个瓶颈会明显影响效率,即数据倾斜。每个线程的处理速度可能不同,快的线程需要等待慢的线程,最终的处理速度取决于慢的线程。而针对此问题,解决的方法是,将数据划分成c×n个partition(c>1),每个线程处理完当前partition后主动取下一个partition继续处理,知道所有数据处理完毕,最后由一个线程进行归并。
(3单机+单核+受限内存
这种情况下,需要将原数据文件切割成一个一个小文件,如采用hash(x)%M,将原文件中的数据切割成M小文件,如果小文件仍大于内存大小,继续采用Hash的方法对数据文件进行分割,知道每个小文件小于内存大小,这样每个文件可放到内存中处理。采用(1)的方法依次处理每个小文件。
(5)多机+受限内存
这种情况,为了合理利用多台机器的资源,可将数据分发到多台机器上,每台机器采用(3)中的策略解决本地的数据。可采用hash+socket方法进行数据分发。
从实际应用的角度考虑,(1)(2)(3)(4)方案并不可行,因为在大规模数据处理环境下,作业效率并不是首要考虑的问题,算法的扩展性和容错性才是首要考虑的。算法应该具有良好的扩展性,以便数据量进一步加大(随着业务的发展,数据量加大是必然的)时,在不修改算法框架的前提下,可达到近似的线性比;算法应该具有容错性,即当前某个文件处理失败后,能自动将其交给另外一个线程继续处理,而不是从头开始处理。
top K问题很适合采用MapReduce框架解决,用户只需编写一个Map函数和两个Reduce 函数,然后提交到Hadoop(采用Mapchain和Reducechain)上即可解决该问题。具体而言,就是首先根据数据值或者把数据hash(MD5)后的值按照范围划分到不同的机器上,最好可以让数据划分后一次读入内存,这样不同的机器负责处理不同的数值范围,实际上就是Map。得到结果后,各个机器只需拿出各自出现次数最多的前N个数据,然后汇总,选出所有的数据中出现次数最多的前N个数据,这实际上就是Reduce过程。对于Map函数,采用Hash算法,将Hash值相同的数据交给同一个Reduce task;对于第一个Reduce函数,采用HashMap统计出每个词出现的频率,对于第二个Reduce 函数,统计所有Reduce task,输出数据中的top K即可。
2.重复问题
在海量数据中查找重复的元素或者去除重复出现的元素也是常考的问题。针对此类问题可以使用位图法实现。
在程序设计中,判断数据是否重复,当集合数据量比较大时,可以采用位图法。首先先扫描一次集合 ,找出集合中最大的元素,然后按照集合中最大元素max创建一个长度为max+q的新数组,接着再次扫面数组,每次遇到一个元素,就将新数组下标为元素值的位置设置为1.
如果使用哈希表的话,最坏情况下这40亿个数都不相同,那么需要16GB的内存,不符合条件。
这个问题我们可以使用位图的方法。首先申请一个大小为4294967295的bit类型的数组bitArr,数组中的每一个位置值表示状态1或者是0.这个类型的数组一共占据500MB的空间满足条件。在使用时,遍历这40亿个无符号数,比如说遇到700就把对应的bitArr[700]设置为1.遍历完成后需要再次遍历哪个位置上没被设置为1那个数就没有出现过。
进阶问题:
(1)根据10GB的内存限制,确定统计区间的大小,就是第二次遍历时的biArr大小
(2)利用区间计数的方式,找到那个计数不足的区间 ,在这个区间上肯定有没有出现过的数。
(3)对这个空间上的数做bitMap映射,在遍历bitMap,找到一个没有出现过的数即可。
对于原问题,可以使用bitMap来表示数出现的情况。具体的说是申请一个长度为4294967295的bit类型的数组bitArr,用两个位置表示一个数字出现的次频。这样一共需要占用1GB的空间。使用时,除此遇到num就将bitArr[num*2+1]和bitArr[num*2]设置为01,第二次出现设置为10,第三次出现设置为11,以后在遇到时不改变。遍历完成之后再进行遍历,如果发现某个位置为10那么就可以找到出现了两次的数。
补充题目和上一个问题的进阶问题很类似,思路如下:
(1)根据10MB的内存限制,确定统计区间的大小,就是第二次遍历时的biArr大小(在本题中长度是2MB)
(2)遍历整个文件,利用区间计数的方式,统计出每个区间的个数,找到中位数可能出现的区间 k。
(3)再次遍历整个文件,这次我们只关心上一步出现的区间k中的数统计区间中各个数出现的个数,这样就可以找到中位数了。
3.海量数据排序问题
3.1数据库排序
导入数据库中,进行索引排序
操作简单,方便,但运行速度慢,对数据库设备要求高
3.2分治法
缩小了每次使用内存的大小,但是编码复杂,速度也慢