求最大的k个数java,java求第K个数的问题(2)

能够改进上面堆排序的做法,仅仅维护一个大小为k的堆吗?

上面的做法为什么不行?因为堆,或者说优先级队列,只有一个出口。换言之,这个最小堆只能每次去poll最小值,如果这个堆的大小已经超过了k,我要是想从中去掉一个肯定不需要的最大值,是没有办法做到的。

但是什么队列有两个出口呢?Deque。可是一般的Deque却又不具备堆的特性,那有没有可能将PriorityQueue和Deque结合起来呢?这样我的问题就解决了。如果需要我自己实现,那我可以分别创建一个最大堆和一个最小堆,分别保存堆元素的引用。代码就不贴了,有上面这些基础,这个很容易实现。

不过开源已经有这样的东西了,有不同的实现版本,有的就叫做PriorityDeque;还有一个版本,是大名鼎鼎的Guava实现的,叫做MinMaxPriorityQueue。

如果这堆数不是放在一起,而是在若干个数组里呢?

前面说了,如果这堆数只在一个数组里,有两种办法可以排序,如果是在若干个不同的数组里呢?一样可以从快排和堆排序两个思路去分析。

如果利用上面改进后的快排,一种方法是合并成一个大数组快排,另一种方法是给每个数组快排之后都各自取最大的k个,拿出来放到一起继续快排。

但是倘若k相对比较小可以接受而某一个数组太大,而且数组太多(假设下面的nums.length ≥ k),那么堆排序就更有优势,因为不需要合并,堆的大小只和这个k有关,和这些数组本身大小无关。具体做法是,如果每个数组都还无序,就先给每个数组排序,如果数组很大,不需要完全有序,只需要用上面的优化了的方法各自整理出容量为k的最小堆,从而使得每个数组都有一个最小堆相对应,能够不断pull出当时该数组最小的元素。于是这个问题就变成了,如何从若干个size为k的最小堆中,找出排第k的元素:

先定义这样一个元素。其中idx就存放着第几个堆(下标),num为实际存放的数值:

class Item {

int num;

int idx;

public Item(int num, int idx) {

this.num = num;

this.idx = idx;

}

}

再在主方法中,对整体维护一个最小堆heap,每次从这个堆中取出一个元素的时候,要观察这个元素是从哪里来的,如果是从nums[i]来的,就再从nums[i]取一个当时的最小元素补充到这个heap中。

public int getKth(Queue nums[], int k) {

Queue heap = new PriorityQueue<>(nums.length, (o1, o2) -> o1.num - o2.num);

for (int i=0; i

heap.add(new Item(nums[i].poll(), i));

Item item = null;

for (int i=0; i

item = heap.poll();

heap.add(new Item(nums[item.idx].poll(), item.idx));

}

return heap.poll().num;

}

这个方法其实还有一个有趣的推广,就是从一维到二维,甚至更高维。

具体来说,如果拿到若干个数组,从中任意取两个数x和y,要求x+y的各种组合里面的第k个,或者在全为非负数的情况下引入乘法,比如x*y+2x的所有组合里面的第k个。这样的问题还是可以基于堆来解决,当然,首先要给每个数组各自排序。思路是类似的。

继续,如果这些数在不同的机器上(文件里)呢?

我想这也是个经典问题,这个问题都问烂了。数据量如果放在一台机器上不合适,那么很多人都会想到,可以map-reduce啊,每台机器上进行map运算都求出最大的k个,然后汇总到一台机器上去reduce求出最终的第k个(如果机器很多,这个汇总过程可以是多级汇总)。

可是,这个回答无意之中假定了一个条件,让问题变得好处理很多。这个条件就是——k不大。

假如这堆数很多,因此放在若干台机器上,但是如果这个k也非常大呢?即便要想把这k个数放到一台机器上去找也不可行。

这时候问题就有点复杂了,也有不同的处理方法。一种办法是,通过某种排序方法(比如基于不断归并的外排序),给每台机器上的数据都排好序,然后从中找一个(猜一个可能为所求的数)数作为pivot,并且在每台机器上这些有序数里面都明确这个pivot的位置。假设machine[i]表示第i台机器上,pivot这个数所处的序列,那么把这些machine[i]累加起来,得到的数sum去和k比较:

◾如果sum==k,那这个数就找到了;

◾如果sum

◾如果sum>k,说明这个数在每台机器上machine[i]往前,直到开头的这一段数中。

如是递归求解。

当然这个方法依然有许多地方可以改进,比如这个预先进行的外排序,未必要完全进行,是可以通过稳重介绍的理论优化掉或者部分优化掉的。

这个方法改变了思考的角度,原本是从一堆数中去找第k个数,现在是从中拿出一个数来,去这堆数中找它应该在的位置。

还蛮有趣的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值