100亿数据找出最大的1000个数字

  • Post author:
  • Post category:其他

这是互联网领域一个比较经典的算法问题(top k),如何在巨大的数据中找出最大,或者访问量最高的前10个,前100个或者前1000个数据。比如在2亿用户记录中找出信用等级最高的,在上亿个搜索词汇中找出被搜索次数最高的10个关键字。前提是数据存储在文件中

一般遇到这个问题,第一反应会想到排序,但是稍微对内存有点了解的人立刻都会否定这个答案,大量的数据导入内存且不说内存够不够,就算足够服务器上其他的服务都不需要内存了吗?那么接下来的思路就是,如果不允许全部导入内存,还要找出最大的1000个数字,那就要用到外部排序(选择)的算法了。

解决的思路有下面几个:

1.采用小顶堆算法,我们知道完全二叉树有几个非常重要的特性,就是假如该二叉树中总共有N个节点,那么该二叉树的深度就是log2N,对于小顶堆来说移动根元素到 底部或者移动底部元素到根部只需要log2N,相比N来说时间复杂度优化太多了(1亿的logN值是26-27的一个浮点数)。基本的思路就是先从文件中取出1000个元素构建一个小顶堆数组k,然后依次对剩下的100亿-1000个数字进行遍历m,如果m大于小顶堆的根元素,即k[0],那么用m取代k[0],对新的数组进行重新构建组成一个新的小顶堆。这个算法的时间复杂度是O((100亿-1000)log(1000)),即O((N-M)logM),空间复杂度是M

这个算法优点是性能尚可,空间复杂度低,IO读取比较频繁,对系统压力大。

2.采用分区法,将100亿个数据分成1000个分区,每个分区上1000万个数据,在每个分区上上再细分成100个分区,即总共分成1000*100个分区,然后启动多线程进行处理,各个分区上采用小顶堆算法取出最大的1000个数据,分层进行合并然后重新计算不同层上的最大1000个数,最终递归到最上层。但linux系统上一个进程能启动的默认线程数是1024个,所以要么调整最大线程数,要么在线程调用处做一些处理,比如一个线程完成一个分区之后再去处理相邻的分区,或者在分区的时候把所有的分区数目限制在1024之内。这个算法切合了map-reduce的思想,利用了多线程和多处理器的优势,减少了多余的比较和IO读取,性能比第一种会更好但算法更复杂一点,要考虑的情况也更多。


版权声明:本文为JackieeeCheng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。