找出一个序列中第k大的元素 [快速选择问题]

1. 基本思路

采用快速排序的方法, 不断在子序列中寻找符合条件的值, 不同于快排的是, 寻找第 k大的元素, 每次只需要对一边的数据进行排序即可.

2. 代码演示

public class SortDemo {

    public static void main(String[] args) {
        int[] arr = new int[] {1, 11, 2, 25, 36, 3, 14, 23, 6, 5, 9, 7, 10};
        int k = 3;
        System.out.println("第 " + k + "大的元素是: " + findMaxN(arr, k));
    }

    // 找出序列中第 k大的元素值
    private static int findMaxN(int[] arr, int k) {
        if (k > arr.length) {
            throw new RuntimeException("k的值不可以大于序列长度");
        }
        return findMaxN(arr, 0, arr.length - 1, k);
    }

    // 寻找最k大的元素 主例程
    private static int findMaxN(int[] arr, int left, int right, int k) {

        // 三数中值法, 获取枢纽元 (基准值)
        int pivot = median3(arr, left, right);

        // 如果子序列长度为 1 or 2 or 3
        // 则不需要走下列步骤, 因为此时子序列已经是有序的了 median3()方法中有排序
        int size = right - left + 1;
        if (size == 1 || size == 2 || size == 3) {
            return arr[arr.length - k];
        }

        // 快速排序主例程
        int i = left;
        int j = right - 1;

        for (; ; ) {
            while (arr[++i] < pivot) {}
            while (arr[--j] > pivot) {}
            if (i < j) {
                swap(arr, i, j);
            } else {
                break;
            }
        }
        // 将枢纽元换回中间的位置
        swap(arr, i, right - 1);

        // 根据 k的范围, 决定对哪一部分(枢纽元左边还是右边)的元素进行再次交换
        if (k <= i) {
            findMaxN(arr, i + 1, right, k);
        } else if (k > i + 1) {
            findMaxN(arr, left, i - 1, k);
        }

        return arr[arr.length - k];
    }

    /**
     * 三数中值分割法:
     *    在序列首部, 末尾和中间三个元素中, 选择值处于中间的元素, 并返回其值
     *    交换首部, 中间和尾部的值, 维持有序性.
     */
    private static int median3(int[] arr, int left, int right) {

        int size = right - left + 1;

        if (size < 1) {
            throw new RuntimeException("子序列长度至少为 1");
        }
        if (size == 1) {
            return arr[left];
        }
        if (size == 2) {
            if (arr[right] < arr[left]) {
                swap(arr, left, right);
            }
            return arr[left];
        }

        int center = (right + left) / 2;

        if (arr[center] < arr[left]) {
            swap(arr, left, center);
        }
        if (arr[right] < arr[left]) {
            swap(arr, left, right);
        }
        if (arr[right] < arr[center]) {
            swap(arr, center, right);
        }

        // 将中值放到尾部之前的元素, 便于前面元素索引的移动
        swap(arr, center, right - 1);

        // 返回中值
        return arr[right - 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;
    }
原文地址:https://www.cnblogs.com/bobo132/p/13950348.html