TopK问题:在大量的数据中找出前k个最大值或者最小值
对于海量数据,如果我们只需要找出其中最大的k个或者最小的k个,怎样才能找出呢?
假设我们对所有的数据进行排序,时间复杂度(快速排序):O(nlog2n); 如果这是数亿个数据,那这样的方法也就不那么高效了
哪哈有没有更加高效的方法呢?
利用堆来解决TopK问题(以最大的k个举例)
我们知道大根堆的堆顶是堆中的最大元素;而小根堆的堆顶是堆中的最小元素;
思路是:我们可以建立一个大小为K小根堆,然后从第k个元素开始遍历,如果大于小根堆的堆顶,那么就交换两个元素,交换一次调整一次,直到所有数据遍历结束,堆中的元素就是海量数据中最大的k个
为什么是小根堆,而不是大根堆呢?大根堆的堆顶是堆中的最大值呀
答:我们的目的是小根堆中保存所有数据中最大的k个,正因为小根堆的堆顶是最小的值,当我们遍历剩余的数据时,只有大于我们这个堆中的最小值,才有资格放进来,这样当遍历结束后堆中的元素就是最大的k个;
为什么建立大小为k呢?
答:因为我们只是想要找出海量数据中最大或最小的k个,所以只需要大小为k;
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /** * 海量数据中最大的k个 * @param arr * @param k * @return */ public static int[] smallKHeap(int[] arr,int k) { int[] kHeap = new int[k]; for (int i = 0; i < k; i++) { kHeap[i] = arr[i]; } //建立大小为k的小堆 for (int i = (k-1-1)/2; i >= 0; i--) { adjustSmallDown(kHeap,i,k-1); } /** * 从下标k开始和堆顶比较,大于堆顶元素交换并且调整 */ for (int i = k; i < arr.length; i++) { if (arr[i] > kHeap[0]){ kHeap[0] = arr[i]; adjustSmallDown(kHeap,0,k-1); } } return kHeap; } //调整 public static void adjustSmallDown(int[] arr,int i,int end) { int parent = i; int child = 2*parent + 1; while(child <= end) { if (child+1 <= end && arr[child+1] < arr[child]) { child = child+1; } //child 下标保存的是最小的值 if (arr[child] < arr[parent]) { int tmp = arr[child]; arr[child] = arr[parent]; arr[parent] = tmp; parent = child; child = 2*parent + 1; }else { break; } } } |
相同的原理求最小的k个就不再累述,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /** * 海量数据中最小的k个 * @param arr * @param k */ public static int[] largeKHeap(int[] arr,int k) { int[] kHeap = new int[k]; for (int i = 0; i < k; i++) { kHeap[i] = arr[i]; } /** * 建大小为k的大堆 -》 求最小得k个数 */ for (int i = (k-1-1)/2; i >= 0; i--) { adjustLargeDown(kHeap,i,k-1); } /** * 从下标为k依次开始和堆顶元素比较,小于堆顶元素 交换 重新调整 */ for (int i = k; i < arr.length; i++) { if (arr[i] < kHeap[0]) { kHeap[0] = arr[i]; adjustLargeDown(kHeap,0,k-1); } } return kHeap; } //调整 public static void adjustLargeDown(int[] arr,int i,int end) { int parent = i; int child = parent*2+1; while (child <= end) { if (child+1 <= end && arr[child+1] > arr[child]) { child = child+1; } //child 保存的是孩子中最大值得下标 if (arr[child] > arr[parent]) { int tmp = arr[child]; arr[child] = arr[parent]; arr[parent] = tmp; parent = child; child = parent*2+1; }else { break; } } } |
性能分析
时间复杂度:O(n*log2k)
空间复杂度:O(k)