设计:优先队列、TOPK问题

TopK引入

又这么几种问题:求出当前数组最小值求出当前数据流中第K大的数
前者是一个固定的范围,而后者是一个变化的范围(当然了,你也可以看作若干个固定范围的快照)。解决以上问题的一个思路是排序,则难以避免需要扫描整个数组的值,而基于堆排序的时间复杂度是O(n*logK)

调整的时间复杂度取决于堆的高度,将K个元素组成一个堆(数组形式的完全二叉树),因此可以调整的时间复杂度就是logK,如果你对整个数组建堆那么调整的时间复杂度就是logN

基于堆去做topK,是一个排除法的思路。

以求当前数组最小值为例,我先拿前K个元素建立一个大顶堆,那么堆顶元素必然是当前最大元素,那么当我遇到第K+1个元素,如果K+1比堆顶元素还大则直接忽略,如果第K+1个元素小于堆顶元素,我就把堆顶元素poll()移除,然后再做一个调整,最终堆顶将又是一个最大元素(比刚才移除的小)…最终遍历完整个数组,我们没做一次调整,就是就是将一个局部最大值移除的过程,有些更大的值甚至不进入offer()。相当于对一个数组移除了n-k个局部最大值,那么剩余的便是K个最小值,而最终堆首便是第K小的值。

总结:堆中构造TopK最大的本质是排除数组中Top(n-k)最小,因此我们需要一个能够快速拿到当前堆最小值的调用,并且排除这个最小的值后调整。
一句话:求Top大用小顶堆,求Top小用大顶堆

    public int[] getLeastNumbers(int[] arr, int k) {
   
        PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
        for(int i:arr){
   
           if(pq.size()<k){
   //大小为K的堆还没有建成
               pq.offer(i);
           }else {
   
               if(!pq.isEmpty()&&pq.peek()>i){
   //排除一个局部最大值,加入一个新元素并调整
                   pq.poll();
                   pq.offer(i);
               }
           }
        }
        int[] res = new int[k];
        int index=0;
        while(k-->0){
   
            res[index++]=pq.poll();
        }
        return res;
    }

java的priorityQueue底层就是一个堆(默认是小顶堆,可以通过构造函数传入比较器对象调整为大顶堆),其中offer()和poll()都涉及堆调整操作。

补充:大数据下的TopK
如果数据多到无法直接载入内存,可以将数据对应的大文件拆分成若干个小文件(至少这些文件对应的数据能够一次载入内存),然后依次读入这些文件,并且在内存中只维护一个大小为K的堆/优先队列,最终这些数据全部被扫描,而且堆/优先队列中保存就是K个数字,堆顶就是对应的Topk

堆排序

以前专门写过排序,这里再说说堆排序

堆排序的过程也是一个排除的过程——假如我们想要对大小为N的数组进行排序,那么我们便可以先对这个数组建立一个大小为N的堆,这个堆其实是一个数组,但是堆建立完毕不代表数组已经有序了,堆建立完毕只是表示:当前堆的堆顶一定是一个最值
而排序的过程就是把这个最值往目标数组移动,然后调整堆的过程。假如我排序升序,我们我可以使用一个大顶堆,每次把堆顶拿走,然后放入到当前数组的末尾,并且对前N-1个堆进行调整。最终使得整个数组都是升序排列的。(注意,因为是对数组整体建堆,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值