1. 基本思路
方法一
利用优先队列的特性(堆序), 在构建出 max堆(大顶堆)之后, 不断将堆顶的元素移除, 就能很轻松的获取前 m个最大的元素.
方法二
对第一种方法的优化, 维护一个含有 m个元素的序列, 在对原始数列进行扫描, 动态调整目标序列, 一次扫描结束之后, 前 m个元素也就找到了.
2. 代码演示
public class SortDemo {
public static void main(String[] args) {
int[] arr = new int[] {1, 2, 3, 4, 5, 7, 8, 10};
printArr(findMaxM(arr, 3));
printArr(findMaxM_2(arr, 3));
}
// 方法一:
// 从给定序列中找出前 m个最大的元素 主例程
private static int[] findMaxM(int[] arr, int m) {
int[] result = new int[m];
// build heap
buildMaxHeap(arr);
// delete max
int count = 0;
for (int i = arr.length - 1; i > 0 && count < m; i--) {
swap(arr, 0, i);
percolateDown(arr, 0, i);
count++;
}
System.arraycopy(arr, arr.length - m, result, 0, m);
return result;
}
// 方法二:
// 维护一个 m个大小的堆, 在对原始数组的扫描中, 动态调整堆, 扫描完成即找到前 m个元素
private static int[] findMaxM_2(int[] arr, int m) {
int[] result = new int[m];
// 从原始数组中取前 m个元素, 用于构建初始堆
System.arraycopy(arr, 0, result, 0, result.length);
buildMaxHeap(result);
// 遍历过程中, 如果当前元素比堆中最小值大的话, 就将其替换掉, 然后调整位置以维持堆序
for (int i = m; i < arr.length && findMin(result) > arr[i]; i++) {
result[findMinIndex(result)] = arr[i];
buildMaxHeap(result);
}
// 对堆中元素进行排序
// 起始这一步也可以不要, 并没有要求前 m大个元素的顺序
deleteMax(result);
return result;
}
private static int findMin(int[] arr) {
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
return min;
}
private static int findMinIndex(int[] arr) {
int index = 0;
int min = arr[index];
for (int i = 1; i < arr.length; i++) {
if (min > arr[i]) {
index = i;
min = arr[index];
}
}
return index;
}
// build max heap
private static void buildMaxHeap(int[] arr) {
for (int i = arr.length / 2; i >= 0; i--) {
percolateDown(arr, i, arr.length);
}
}
// delete max
private static void deleteMax(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i);
percolateDown(arr, 0, i);
}
}
// 下滤操作
private static void percolateDown(int[] arr, int i, int n) {
int child; // 保存与父节点产生交换操作的 子节点的索引
int tmp; // 保存被调整位置元素的值
// tmp记录被调整元素的值, i的值随 child不断改变, 直到比较完所有的子节点 (leftChild(i) < n) 为止
for (tmp = arr[i]; leftChild(i) < n ; i = child) {
child = leftChild(i);
// child == n - 1时, 其左子节点为堆的最后一个元素, 没有右节点, 无须比较
// 将该节点与其左右节点比较, 记录其中最小的节点的索引
if (child != n - 1 && arr[child] < arr[child + 1]) {
child++;
}
// 将需要被调整的节点与其子节点进行比较, 如果小于子节点, 当前节点的值替换为子节点的值 (注意不是交换)
if (tmp < arr[child]) {
arr[i] = arr[child];
} else {
break;
}
}
// 找到合适的位置后, 直接赋值来避免多余的交换操作
// 注意 i值的变化, 在调整位置过程中, 如果改变了子序列的堆序, 也要立即调整过来
arr[i] = tmp;
}
// 获取指定节点处元素的左节点索引
private static int leftChild(int i) {
return 2 * i + 1;
}
// 交换数组中两个元素的位置
private static void swap(int[] arr, int i, int j) {
if (i > arr.length - 1 || i < 0 || j > arr.length - 1 || j < 0) {
throw new RuntimeException("索引值有误");
}
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// 打印数组元素
private static void printArr(int[] arr) {
System.out.println(Arrays.toString(arr));
}
}