题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,
求最大的k个数用最小堆,根节点是堆 的最小值,每次和根节点比较,比根小,舍弃,否则,代替根
求最小的K个数用最大堆,根节点是堆的最大值,每次和根节点比较,比根大,舍弃,否则,代替根。
PriorityQueue 是默认小根堆,根节点是最小值。所以用来求解最大的k个数
public static List<Integer> findKthLargest(int[] nums, int k) { PriorityQueue<Integer> minQueue = new PriorityQueue<>(k); for (int num : nums) { if (minQueue.size() < k || num > minQueue.peek()) minQueue.add(num); if (minQueue.size() > k) minQueue.poll(); }
return new ArrayList<>(minQueue);
}
本题解法:PriorityQueue 是可以指定为大根堆,根节点是最大值。所以用来求解最小的k个数
public static List<Integer> solutionByHeap(int[] input, int k) { List<Integer> list = new ArrayList<>(); if (k > input.length || k == 0) { return list; }
//指定为大根堆。 PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder()); //得到k个最大值 for (int num : input) { if (queue.size() < k) { queue.add(num); } else if (queue.peek() > num){ queue.poll(); //移除头元素,也就是最大值 queue.offer(num); } }
return new ArrayList<>(minQueue);
}
本题解法:手写大根堆,前k个元素是最小的k个元素 为什么从n/2开始建堆呢,因为n/2是最后一个非叶子节点。n/2到n的所有节点都是叶子节点,是最下一层,不需要下沉。比如n=9 树的编号是{[1],[2,3],[4,5,6,7],[8,9]}。n/2=4就是第三层的第一个节点,也是最后一个非叶子节点。
/* 维护 A[0...k-1] 这个大根堆 */ public static List<Integer> topKSmall(int A[], int n, int k) { //初始化前面k个元素 for(int i=k/2; i>=0; --i) AdjustDown(A, i, k); // 先用前面的k个数建大根堆 // 从k开始向后遍历,一直到结束,比如k=3,n可能是很大很大。 for(int i=k; i<n; ++i) { if(A[i] < A[0]) // 如果小于堆顶元素,替换之 { int tmp = A[0]; A[0] = A[i]; A[i] = tmp; AdjustDown(A, 0, k); // 向下调整 } } ArrayList<Integer> result = new ArrayList<Integer>(); for (int i = 0; i < k; i++) { result.add(A[i]); } return result; } /** * * @param arr * @param parentIndex * @param length 指的是堆的有效大小。只在这个范围内下沉 */ public static void AdjustDown(int arr[], int parentIndex, int length) { int temp = arr[parentIndex]; int childIndex = 2 * parentIndex +1; while (childIndex<length) { if (childIndex+1<length && arr[childIndex+1]>arr[childIndex]){ childIndex++; } if(temp>=arr[childIndex]) break; arr[parentIndex]=arr[childIndex]; parentIndex=childIndex; childIndex=2*childIndex+1; } arr[parentIndex]=temp; }
当求最大的k个数字时,只需要把大根堆换成小根堆即可。(下沉的判断条件反一下,入堆的判断条件也反一下)
/* 维护 A[0...k-1] 这个小根堆 */ public static List<Integer> topKBig(int A[], int n, int k) { //初始化前面k个元素 for(int i=k/2; i>=0; --i) AdjustDown(A, i, k); // 先用前面的k个数建小根堆 // 从k开始向后遍历,一直到结束,比如k=3,n可能是很大很大。 for(int i=k; i<n; ++i) { if(A[i] > A[0]) // 如果大于堆顶元素,替换之 { int tmp = A[0]; A[0] = A[i]; A[i] = tmp; AdjustDown(A, 0, k); // 向下调整 } } ArrayList<Integer> result = new ArrayList<Integer>(); for (int i = 0; i < k; i++) { result.add(A[i]); } return result; } /** * * @param arr * @param parentIndex * @param length 指的是堆的有效大小。只在这个范围内下沉 */ public static void AdjustDown(int arr[], int parentIndex, int length) { int temp = arr[parentIndex]; int childIndex = 2 * parentIndex +1; while (childIndex<length) { if (childIndex+1<length && arr[childIndex+1]<arr[childIndex]){ childIndex++; } if(temp<=arr[childIndex]) break; arr[parentIndex]=arr[childIndex]; parentIndex=childIndex; childIndex=2*childIndex+1; } arr[parentIndex]=temp; }
总结:大根堆的解法和堆排序几乎一样,只有两个地方不同,1:大根堆解法只需要对前k个元素建堆,堆排序要对所有元素建堆。2.大根堆解法每次替换堆定元素,堆排序每次是删除堆定元素,把它移到未排序部分 的最后一位
//堆排序 public static void heapSort(int[] arr){ for(int i = arr.length/2;i>=0;i--){ downAdjust(arr,i,arr.length); } System.out.println(Arrays.toString(arr)); //删除堆顶的元素,就是把数组的第一位和最后一位换位置 for (int i = arr.length-1;i>0;i--) { int temp = arr[i]; arr[i]=arr[0]; arr[0]=temp; downAdjust(arr,0,i); //然后把第一个元素下沉,在0到length的区间内 System.out.println(Arrays.toString(arr)); } } public static void downAdjust(int[] arr,int parentIndex,int length) { int temp = arr[parentIndex]; int childIndex = 2 * parentIndex +1; while (childIndex<length) { if (childIndex+1<length && arr[childIndex+1]>arr[childIndex]){ childIndex++; } if(temp>=arr[childIndex]) break; arr[parentIndex]=arr[childIndex]; parentIndex=childIndex; childIndex=2*childIndex+1; } arr[parentIndex]=temp; }