Top K问题:顾名思义,就是给你多个数,让你找出最大的或者最小的K个数。
分析:通常情况下,数量级都是千万级别的,数据量特别大,所以肯定不能先排序,然后再遍历取出K个数。
我们以求出最小的K个数为例进行分析。既然一个大顶堆的顶是最大的元素,那我们要找最小的K个元素,可以先建立一个包含K个元素的大顶堆,然后遍历集合,如果集合的元素比堆顶元素小(说明它目前应该在K个最小之列),那就用该元素来替换堆顶元素,同时维护该堆的性质,那在遍历结束的时候,堆中包含的K个元素就是我们要找的最小的K个元素。
算法实现如下:
public class TopK {//最小的k个元素 public static int[] topK(int[] nums, int k){ 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){//针对某个节点进行调整,使之符合大顶堆 int i=index; int j=2*(i+1)-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; } } } }
堆排序做TopK算法有如下几个特点:
1、不会改变数据的输入顺序;
2、不会占用太多的内存空间(事实上,一次只读入一个数,内存只要求能容纳前K个数即可);
3、由于2,决定了它特别适合处理海量数据。
此外,还有一点要注意:要找出最小的K个元素使用大顶堆,求最大的K个元素使用小顶堆。小对大,大对小,很好记。
附:求最大的K个元素的算法实现(和前面的代码一毛一样)
public class TopK2 {//最大的k个元素 public static int[] topK2(int[] nums, int k){ 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); } 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){ int i=index; int j=2*(i+1)-1; while(j<nums.length){ if(j+1<nums.length && nums[j]>nums[j+1]){ 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; } } } }