竹石

作者:郑燮

咬定青山不放松,立根原在破岩中。

千磨万击还坚劲,任尔东西南北风。

前言

又到了一年一度的南北人口大迁移的时候,没有买票的赶紧买票,今年很早就已经回家准备过年了,因为小编已经离职啦,最近正在积极复习找工作,闲话不多扯,开始今天的正题。

面试题目:如何在10亿个整数中找出前1000个最大的数。

这就是有名的TopN问题,这样的问题有很多种解法,下面我对我了解的解法做一个总结并写出最优算法。

如果初次听到这样的题目,我相信大家和我的第一反应都是一样的,先排序后输出前1000个,那么多排序,归并排序、快速排序、堆排序。。。

那么问题来了,排序的复杂度太大,数据量又是上亿级别的,明显排序方案不合理。那么我们能否不要全排,只排序部分元素,不就可以了吗?

方法一:冒泡排序

由此想到冒泡排序的原理:通过两层for循环,外层第一次循环找到数组中最大的元素放置在倒数第一个位置,第二次循环找到第二大的元素放置在倒数第二个位置。。。循环N次就可以找到TopN。

缺点:冒泡排序内层循环需要大量交换元素。复杂度介于O(n)和O(n^2)之间。

方法二:分而治之

由快速排序原理可知:选一个基准元素,每次排序可以将这个基准元素搁置在正确的位置,左边都是比基准小的元素,右边都是比基准大的元素从而将数组分成左右两部分,分而治之。TopN问题也同样如此,选择一个基准元素并通过快速排序将基准元素搁置在正确的位置,如果左边的元素个数小于1000,那么继续从基准右边排序,如果左边元素个数大于1000,那么从基准左边排序,直到基准的位置正好在1000,结束。

缺点:第一次排序复杂度是O(n),第二次排序复杂度是O(n/2),第三次排序复杂度是O(n/4)...

方法三:文件存储,分而治之

将比基准小的元素存储在txt1中,比基准大的文件存储在txt2中,然后通过类似方法二的形式,最后求出TopN。

缺点:磁盘读取,写入次数过多。

方法四:分布式、MapReduce

单机内存和性能确实受限,那么我们可以将10亿个分段存储在不同的机器上,每台机器计算各自的TopN,最后汇总。

缺点:空间换时间。

当然,上述方法均不合理,TopN的比较好的方法是通过——

最优解:小顶堆

如果大家对堆排序的原理不清除,可以查阅相关资料,我推荐大家参考《算法导论》第六章堆排序,讲解很详细。如果大家需要堆排序的源码,可以参考笔者这篇堆排序文章,本文对堆排序不在赘述。

首先,我们需要构建一个大小为N的小顶堆,小顶堆的性质如下:每一个父节点的值都小于左右孩子节点,然后依次从文件中读取10亿个整数,如果元素比堆顶小,则跳过不进行任何操作,如果比堆顶大,则把堆顶元素替换掉,并重新构建小顶堆。当10亿个整数遍历完成后,堆内元素就是TopN的结果。

下面,就写一下代码吧

public class TopN {public static int N = 10;           //Top10public static int LEN = 100000000; //1亿个整数public static int arrs[] =  new int[LEN];public static int arr[] = new int[N];//数组长度public static int len = arr.length;//堆中元素的有效元素 heapSize<=lenpublic static int heapSize = len;public static void main(String[] args) {//生成随机数组for(int i = 0;i<LEN;i++){arrs[i] = new  Random().nextInt(999999999);}//构建初始堆for(int i =  0;i<N;i++){arr[i] = arrs[i];}//构建小顶堆long start =System.currentTimeMillis();buildMinHeap();for(int i = N;i<LEN;i++){if(arrs[i] > arr[0]){arr[0] = arrs[i];minHeap(0);}}System.out.println(LEN+"个数,求Top"+N+",耗时"+(System.currentTimeMillis()-start)+"毫秒");print();}/*** 自底向上构建小堆*/public static void buildMinHeap(){int size = len / 2;for(int i = size;i>=0;i--){minHeap(i);}}/*** i节点为根及子树是一个小堆* @param i*/public static void minHeap(int i){int l = left(i);int r = right(i);int index = i;if(l<heapSize && arr[l]<arr[index]){index = l;}if(r<heapSize && arr[r]<arr[index]){index = r;}if(index != i){int t = arr[index];arr[index] = arr[i];arr[i] = t;//递归向下构建堆minHeap(index);}}/*** 返回i节点的左孩子* @param i* @return*/public static int left(int i){return 2*i;}/*** 返回i节点的右孩子* @param i* @return*/public static int right(int i){return 2*i+1;}/*** 打印*/public  static void print(){for(int a:arr){System.out.print(a+",");}System.out.println();}

由于机器内存首先,我就模拟了1亿个数求Top10,并计算时间。

核心代码,不精确的测试,来看看性能如何:

100000000个数,求Top10,耗时159毫秒
999999910,999999924,999999931,999999946,999999959,999999979,999999953,999999973,999999961,999999982,100000000个数,求Top1000,耗时157毫秒
100000000个数,求Top10000,耗时159毫秒
100000000个数,求Top30000,耗时187毫秒
100000000个数,求Top60000,耗时223毫秒
200000000个数,求Top10000,耗时321毫秒
300000000个数,求Top10000,耗时482毫秒
//4亿个数字,OOM
java.lang.OutOfMemoryError: Java heap space
at com.stx.sort.TopN.<clinit>(TopN.java:8)
Exception in thread "main"
Process finished with exit code 1

简直速度快到没女朋友。。。

与TopN类似的题目并且应用堆原理的问题有很多,再给大家举个例子哈:

记得上周,小编和同事聚餐,期间有个同事提出了一个问题:

给一个无序数组,元素个数亿级,要求写一个算法,要求最后数组左边的任何一个数都比数组右边任何一个数小,但左右数组中元素是否有序不要求。于是,热闹的饭局一下子安静下来,其他同事们都陷入了深深的思考。

了解完堆和TopN的原理后,这样的题也很容易,解法如下:

  • 数组长度为len,构建一个大小为n=len/2的大顶堆,从n处遍历数组

  • 如果元素小于堆顶元素,将该元素与堆顶元素交换,并重新构建大顶堆

  • 若该元素大于堆顶元素,则不操作,继续遍历

  • 直到遍历完成,此时堆中的数据就是左半部分小的数组,与原数组合并后就是需要的结果

代码如下:

public class TopN {public static int LEN = 20;     //数组大小public static int N = LEN/2;    //堆大小public static int arrs[] = new int[LEN];public static int arr[] =  new int[N];   //堆//数组长度public static int len = arr.length;//堆中元素的有效元素 heapSize<=lenpublic static int heapSize = len;public static void main(String[] args) {//初始化数组for(int i = 0;i<LEN;i++){arrs[i] = new Random().nextInt(1000);}for(int i = 0;i<N;i++){arr[i] = arrs[i];}//构建大顶堆buildMaxHeap();for(int i = N;i < LEN;i++){//如果比堆顶元素小,交换两个数的位置,并重新调整堆结构if(arrs[i] < arr[0]){int t = arrs[i];arrs[i] = arr[0];arr[0] = t;maxHeap(0);}}//修改原数组for(int i = 0;i<N;i++){arrs[i] = arr[i];}print();}/*** 自底向上构建大堆*/public static void buildMaxHeap(){int size = len / 2;for(int i = size;i>=0;i--){maxHeap(i);}}/*** i节点为根及子树是一个大堆* @param i*/public static void maxHeap(int i){int l = left(i);int r = right(i);int index = i;if(l<heapSize && arr[l]>arr[index]){index = l;}if(r<heapSize && arr[r]>arr[index]){index = r;}if(index != i){int t = arr[index];arr[index] = arr[i];arr[i] = t;//递归向下构建堆maxHeap(index);}}/*** 返回i节点的左孩子* @param i* @return*/public static int left(int i){return 2*i;}/*** 返回i节点的右孩子* @param i* @return*/public static int right(int i){return 2*i+1;}/*** 打印*/public  static void print(){for(int a:arrs){System.out.print(a+",");}System.out.println();}
601,425,389,403,368,164,292,344,305,134,918,896,838,789,695,690,666,663,609,734,Process finished with exit code 0

结束

堆在生活中应用很广泛,再比如说——优先队列,相信聪明的大家看完我这篇文章后都能写出优先队列。

希望大家可以从原理上掌握堆,在日后的面试中和工作中灵活应用,关注小编公众号或添加小编为微信好友,获取更多技术。提前给大家拜早年,祝各位能顺利进入BATJ。

【算法面试】TopN问题相关推荐

  1. 机器学习与算法面试太难?

    机器学习与算法面试太难? 来源: https://mp.weixin.qq.com/s/GrkCvU2Ia_mEaQmiffLotQ 作者:石晓文 八月参加了一些提前批的面试,包括阿里.百度.头条.贝 ...

  2. 摊牌了:我就靠这几点,搞定了算法面试官

    很多时候,你即使提前复习了这些最常见的面试算法题,你依旧无法通过算法面试! 为什么? 1. 你在提前准备复习的时候,在网上找了半天相应题目的分析文章,但你看了就是不懂. 2. 你在面试的时候,卡壳了, ...

  3. 4种最常问的编码算法面试问题,你会吗?

    导语:面试是测查和评价人员能力素质的一种考试活动.最常问的编码算法面试问题你知道多少呢? 作者 | Rahul Sabnis 译者 | 苏本如,编辑 | 刘静 来源 | CSDN(ID:CSDNnew ...

  4. 《大厂算法面试题目与答案汇总,剑指offer等常考算法题思路,python代码》V1.0版...

    为了进入大厂,我想很多人都会去牛客.知乎.CSDN等平台去查看面经,了解各个大厂在问技术问题的时候都会问些什么样的问题. 在看了几十上百篇面经之后,我将算法工程师的各种类型最常问到的问题都整理了出来, ...

  5. 算法面试的理想与现实

    2020-01-11 16:00:12 大型科技公司通常都主张必须进行算法面试,因为他们的规模过大,无法承受低效代码带来的巨额成本.但一次的算法面试真的能体现一个人真正的实力吗? 作者 | Dan L ...

  6. 几道 BAT 算法面试中经常问的「字符串」问题

    https://www.toutiao.com/a6675839856192520711/ String 作为最常见的编程语言类型之一,在算法面试中出现的频率极高. 1. 验证回文串 题目来源于 Le ...

  7. 前 Google 工程师总结的算法面试指南

    作者 | 小争哥 出品 | 小争哥 (ID:xiaozhengge0822) 为什么要学习算法和数据结构?尽管原因有很多,比如锻炼逻辑思维能力.编码能力.阅读源码的能力等等,但我想对于大多数人来说,最 ...

  8. NLP中文面试学习资料:面向算法面试,理论代码俱全,登上GitHub趋势榜

    鱼羊 发自 凹非寺  量子位 报道 | 公众号 QbitAI 一年一度的秋招已经悄悄来临,征战在求职路上的你,准备好了吗? 工欲善其事,必先利其器. 这里有一份NLP面试大全,全中文教学,囊括机器学习 ...

  9. 算法面试:精选微软经典的算法面试100题(第1-20题)

    算法面试:精选微软经典的算法面试100题 引言: 给你10分钟时间,根据上排给出十个数,在其下排填出对应的十个数 要求下排每个数都是先前上排那十个数在下排出现的次数. 上排的十个数如下: [0,1,2 ...

  10. 横空出世,席卷Csdn [评微软等公司数据结构+算法面试100题]

    横空出世,席卷Csdn ---评微软数据结构+算法面试100题 作者:July 时间:2010年10月-11月.版权所有,侵权必究. --------------------------------- ...

最新文章

  1. 好久没写blog了。最近感觉自己老了
  2. 移动端,input输入框被手机输入法解决方案
  3. USTC服务器使用笔记
  4. ajax隐藏button,jquery下的ajax应用-form和button触发
  5. oracle 10g db_file,oracle 10g的db_file_multiblock_read_count参数
  6. arm-linux-ld segment fault,segment fault 定位 与 远程 gdb
  7. Nginx 502报错(django+nginx,而非php-fmp)
  8. 2019级C语言大作业 - 冷冻双侠
  9. 描述java泛型引入原则_Java/泛型的类型擦除/README.md · oslo/LearningNotes - Gitee.com
  10. php func_get_args(),PHP中func_get_args(),func_get_arg(),func_num_args()有什么不同
  11. logitech鼠标接收器配对
  12. 如何恢复eclipse默认(初始)界面
  13. 一文搞懂三级管和场效应管驱动电路设计及使用
  14. 在线html5编辑器uedit,ueditor集成秀米编辑器 - HTML - php中文网博客
  15. RabbitMQ学习笔记:消息优先级(priority)
  16. 流浪动物救助服务网站设计与实现(J2EE)
  17. 如何解决王者荣耀排位赛中的系统制裁
  18. 开放原子训练营(第一季)铜锁探密,SM3杂凑算法加强至pro版
  19. 苹果Safari怎么打开html,苹果内置safari浏览器怎么用?safari浏览器详细使用教程...
  20. gnuplot 等高线脚本

热门文章

  1. 关于无穷级数收敛的充要条件的猜想
  2. Astah Professional三维图,网络上轻松上传图表
  3. 免费高清图片素材网站
  4. MongoDB下载、安装和配置教程
  5. 西门子PLC丨虚拟机运行仿真器供物理主机使用
  6. eplan2.4安装教程
  7. 我也来开发2048之确定思路
  8. Java实例(1)BMI计算
  9. 小车yolo机械臂(四)python ros 和darknet_ros 使用launch文件启动脚本
  10. Sublime Text 3破解补丁