首先,什么是 TopK 问题?
就是给定一个集合(元素个数为N),找到前 K 个最大和最小的元素
解决方法
· 方法一:针对整个集合,建立一个大小为 N 的大堆,循环取 K 次堆顶元素;
· 方法二:建立一个大小为 K 的小堆(求前K大的值),令堆顶元素为守门员,循环遍历 N 个元素中的每个元素,当元素大于堆顶元素时,将守门员替换成此元素,并进行向下调整,得到新的守门员,当所有元素遍历完后,堆中剩下的就是前 K 大的值;得到的前 K 个元素是无序的,想要知道谁是第几大,得重新排序
两种方法的使用场景
当 N >>K 时,第一种方法的时间效率和空间效率远低第二种方法,所以方法二更优
当 N 很大时,N个元素无法同时加到内存中,也只能使用方法二
当 N 和 K 差不多大时,两种方法都可以,差别不大
方法二程序代码如下:
public class HeapTopK {
private int[] KSmallNum(int[] array,int k) {
if(k <= 0 || k > array.length) {
return array;
}
//PriorityQueue<Integer> queue = new PriorityQueue<>();
int[] ret = new int[k];
// 先将 array 前 K 个元素加到 ret中
for(int i = 0; i < k; i++ ) {
ret[i] = array[i];
}
// 建小堆
for(int i = (k-1-1)/2; i >= 0; i--) {
shiftDown(ret, k, i);
}
// 循环拿堆顶元素与array中的其他元素进行比较
while (k < array.length) {
if(ret[0] < array[k]) {
ret[0] = array[k];
shiftDown(ret,ret.length,0);
}
k = k+1;
}
return ret;
}
private void shiftDown(int[] array, int size, int index) {
int parent = index;
int child = 2*parent + 1;
while(child < size) {
if(child+1 < size && array[child+1] < array[child]) {
child = child + 1;
}
if(array[child] < array[parent]) {
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else {
break;
}
parent = child;
child = 2*parent + 1;
}
}
public static void main(String[] args) {
HeapTopK heapTopK = new HeapTopK();
int[] array = {9,6,5,2,1,4,7,8};
int[] ret = heapTopK.KSmallNum(array, 4);
// 打印出来的结果是无序的,是前K大的小堆层序遍历的结果
System.out.println(Arrays.toString(ret));
// 想要得到是第几大的值,就得重新排序
}
}