PriorityQueue的实践
PriorityQueue内部维护着一个堆,如果不传入比较器,则按自然排序,实际上维护着一个小顶堆。传入比较器后,把比较后小的值放在堆顶。
接下来我们看这样一个问题:如何得到一个数组最小的k个数。
解决该问题有两个思路:
-
利用Partition函数,得到数组中第n小的下标n,如果n=k-1,则前n个数则为最小的k个数;如果n<k-1,则对后面的数继续进行Partition排序;如果n>k-1,则对前面的数继续进行Partition排序,直到n=k-1;
-
建立一个容量为k的大堆顶,并从数组中逐个取数,如果堆的容量小于k,则向堆中添加该数;如果堆的容量等于k,则比较取的数和堆顶元素。堆顶元素始终是堆中元素的最大值,如果取出的数比堆顶元素小,则删除堆顶元素而把该数加入堆中。当遍历一次数组后,堆中的k个数即为数组中最小的k个数。
由于堆类似于一棵完全二叉树,因此每次的删除添加操作的时间复杂度都为logk,故整个算法的时间复杂度为O(nlogk)。该算法非常适用于处理海量数据(所有元素不必一次都读入内存中),当n很大,k很小的时候。
下面是代码实现:
private static ArrayList<Integer> theMinkOfArray(int[] array, int k) {
ArrayList<Integer> result = new ArrayList<>();
if (array == null || array.length <= 0) throw new RuntimeException("Invalid input");
if (k == 0) return null;
if (array.length < k) throw new RuntimeException("Invalid input");
PriorityQueue<Integer> queue = new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
for (int i=0; i < array.length; i++){
if (queue.size()<k){
queue.add(array[i]);
}
else {
if (queue.peek() > array[i]){
queue.poll();
queue.add(array[i]);
}
}
}
for (int i=1; i<=k; i++){
Integer min = queue.poll();
result.add(min);
}
return result;
}