10亿数据找到前100大的数(Top K问题)

Top K问题
在大规模数据处理中,经常需要处理Top K问题:在乱序数据中找到前K个数据。例如在海量搜索结果中找到权重最高的前K个结果。

针对Top K类问题,通常最好的方案是使用分治 + 小顶堆。即先将数据集用Hash方法拆解成多个小数据集进行分治,然后用小顶堆在每个数据集中找到最大的前K个数,最后在所有小数据集的Top K数中通过系统排序找到最终的Top K个数。

问题描述
在10亿行浮点数的文件中找到最大的前100个数字。

为后续计算复杂度,我们记n数据总长度,K为需要取出的最大值的个数。

思路一:直接排序
1. 思路
最直接的思路就是将数据全部排序后取最后100个数。

2. 复杂度
时间复杂度:使用快速排序对数据排序时复杂度为 [公式]
空间复杂度:快速排序需要将数据全部加载到内存中,单个float32位的浮点数占4个字节,10亿浮点数就需要占用4G内存,对于一些可用内存较少的计算机显然是无法将数据一次性加载到内存中的
3. 缺点
占用内存大
浪费性能:目的是求出最大的100数,对所有数字排序显然是无用功
思路二:局部淘汰法
1. 思路
最极端时K为1,那么我们可以自己实现max函数找到序列最小值,时间复杂度为 [公式] ,空间复杂度为 [公式] 。当K大于等于2时,我们需要维护一个长度为K的序列来保存最大的K个数,具体思路如下:

step1:使用一个数组存储文件前100个浮点数,并排好序,记为序列L
step2:遍历文件中剩余的数字,如果比序列L中最小值还要小则直接丢弃,否则通过插入排序的方式插入序列L并删掉最小数,最终得到的序列L就是前100大的数
2. 复杂度
时间复杂度:最坏情况下文件中每个数都需要遍历一遍序列L找到插入的位置,复杂度为 [公式] ;最好情况下一开始选择的100个数就是最大的,复杂度为 [公式]
空间复杂度:所用空间就是序列L的长度,即 [公式]
继续优化:以小顶堆的方式来维护序列L,具体看后面的思路四。
思路三:快速排序
1. 思路
step1:以数组最后一个元素作为基准值,将数据partition为[left,mid-1]和[mid,right]两个区间,其中[left,mid-1]的数都小于base值,[mid,right]都大于等于base值
step2:计算[mid, right]数组长度L:
如果L等于K那么[mid,right]这些数字就是最终要找到的Tok K个数
如果L大于K则对[mid, right]重复step1操作
如果长度小于K说明[mid, right]中都是Top K的数,我们将问题转化为在[left, mid-1]中寻找Top K - L个数(如果L较小时也可以对partition前的数据[left, right]直接进行排序,然后取最大的K个数即可)

2. 复杂度
时间复杂度:假设每次partition都比较均匀,那么第一次partition时需要遍历n个数据,第二次partition时数据量减半需要遍历n/2个数据,第三次partition需要遍历n/4个数据…以此类推最终花费的总时间趋近于2n,因此时间复杂度是 [公式]
空间复杂度:10亿个float32位的浮点数大概需要4G内存,对于内存较小的计算机而言无法一次性加载到内存中;如果每次都将pairtion后的两份数据存入两份文件,就会造成多次磁盘IO从而极大降低算法速度
思路四:分治法
1. 思路
将数据分散成多份,通过多台机器分布式运算或者多线程并发计算的方式取得每份数据的Top K,然后汇总结果。

2. 优点
可以解决快速排序等思路中对计算机内存要求较大的问题。

思路五:小顶堆
1. 思路
取文件中前K个数在内存中维护一个长度为K的小顶堆,然后从文件中挨个读取数字并和堆顶比较,如果比堆顶小则直接丢弃,否则替换堆顶后调整小顶堆。遍历完文件中所有的数字后,小顶堆中的K个数就是所求的Top K。

2. 优点
只需要遍历一次文件中的数字,不存在多次读写数据的问题。

3. 复杂度
时间复杂度:最好情况下文件中前K个数就是Top K,遍历一遍文件即可,时间复杂度为 [公式] ;最坏情况下遍历文件中每个数都需要调整小顶堆,时间复杂度为 [公式]
空间复杂度:只需要在内存中维护小顶堆,空间复杂度为 [公式]
4. 优化
可以通过并行计算(多线程或者分布式运算)的方式进一步提高算法效率:

通过Hash方法将n个数据随机切分成m份,需要的时间复杂度为 [公式]
对于m份数据并行使用小顶堆选出最大的k个数据,需要的时间复杂度为 [公式] ,运算完后得到m组长度为k的小顶堆
并行对上步每一个小顶堆进行堆排序,得到m个长度为k的有序序列,时间复杂度为 [公式]
二分查找取出m个有序序列中最大的数,遍历k次即可,时间复杂度为 [公式]
由于第二步和第三步可以并行计算,因此总的时间复杂度为:

[公式]

对比没有并行的小顶堆算法,可以发现算法得到了常数级别的优化。

实际情况
实际处理大数据Top K问题时,需要考虑两个问题:

是否并发:并发可以显著提高运行速度,单机多核可以使用Hash将数据划分为多份子数据然后多线程并发排序,多机可以使用Hash+Socket方法将数据分发到多台机器上
是否有足够内存:如果机器内存足够可以直接在内存中使用Hash对数据进行切分,如果机器内存不足可以将原始文件切分成多个小文件
在实际开发中,如果机器有足够内存就直接在内存中处理即可,机器有多个核时也可以使用多线程并发处理。

变式
1. 从十亿数据中找到出现次数最多的十个数字
用hash树记录每个数字出现的频率,转化为在各个数字的频率中找到Top K的问题。

2. 从十亿数据中找到升序的第七亿个数
2.1 快速排序

step1:以数组最后一个元素作为基准值,将数据partition为[left,mid-1]和[mid,right]两个区间,其中[left,mid-1]的数都小于base值,[mid,right]都大于等于base值
step2:计算[mid, right]数组长度L:
如果L等于三亿,那么[left,mid-1]中的七亿个数字小于[mid,right]中的三亿个数字,只需要在[left,mid-1]中找到最大值即可
如果L大于三亿,则将问题转化为在[mid, right]中找到第(L-三亿)个数
如果L小于三亿则问题还是在[left,mid-1]中找到第七亿大的数
在step2中,如果数据量级足够小则可以直接进行排序得到答案。
2.2 桶排序

假如数据的范围是有限的(有最大值和最小值)且较均匀分布,那么使用桶排序可以进一步提升效率:

step1:获取数据的max和min
step2:将所有的n份数据分到m个文件中,同时记录每份子文件中的数据量
step3:从小到大累加文件数据量,找到第七亿个数所在的文件,记录前面文件的总数据量count
step4:将问题转化为在第七亿个数所在的文件中寻找第(七亿-count)个数
在step4中,如果内存足够存放所有数据,可以直接排序获取第七亿个数字。
3. 对十亿数据进行排序
多路归并排序。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值