问题:从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。
1、Java基于TreeSet实现
public static void main(String[] args) throws Exception, RemoteException {
int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 8 };
TreeSet<Integer> set = new TreeSet<Integer>();
for (int i = 0; i < a.length; i++) {
int value = a[i];
if (set.size() < 5)
set.add(value);
else {
Iterator<Integer> it = set.descendingIterator();
int setMax = it.next();
if (setMax > value) {
it.remove();
set.add(value);
}
}
}
System.out.println("topk之后:" + set);
}
2、基于大顶堆的top k算法
我们先创建一个大小为k的数组来存储最小的k个数字,接下来我们从输入的n个数中读取一个数,如果容器还没有满则直接插入容器;若容器已经满了,则我们此时需要将容器中最大的数字和待插入的数字做比较,如果待插入的数值小于容器中的最大值,则需要将容器中的最大值删除,将新值插入,否则不做任何处理。这种算法适合海量数据的情况下。
public static void main(String[] args) throws Exception, RemoteException {
int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1, 8 };
System.out.println("top k结果:" + Arrays.toString(topK(a, 5)));
}
public static int[] topK(int[] nums, int k) {
if (k >= nums.length)
return nums;
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = nums[i];
}
for (int i = (k - 1) / 2; i >= 0; i--) {// 建一个初始堆
sift(res, i);
}
// 从nums中第k项开始,与大顶堆的根节点依次比较,并进行调整
for (int i = k; i < nums.length; i++) {
if (nums[i] < res[0]) {
res[0] = nums[i];
sift(res, 0);
}
}
return res;
}
private static void sift(int[] nums, int index) {// 针对某个节点进行调整,使之符合大顶堆
if (index >= nums.length)
return;
int i = index;
int j = 2 * i + 1;
while (j < nums.length) {
if (j + 1 < nums.length && nums[j] < nums[j + 1]) {
j++;// j指向较大的子节点
}
if (nums[i] < nums[j]) {// 调整节点
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i = j;// 追踪调整
j = 2 * (i + 1) - 1;
} else {
break;
}
}
}