从 n个长度的序列中找出前 m大个元素

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));
    }
}
原文地址:https://www.cnblogs.com/bobo132/p/13950344.html