查找第K大/小元素算法

问题描述

就是给出一个随机序列,序列元素可比较,查出第K大元素或者第K小元素。
这是一个经典的算法题,之前也写过,这里总结一下思路。

思路一:先快排再直接取元素

对于一个随机序列,快排的性能应该是最好的啦(基础排序算法),稍加优化性能更佳。
我们先对序列用 数组/顺序 表存储起来,再快排(如果懒得写,Java可用java.util.Arrays.sort(),C++可用STL的sort())。
排完以后就利用其直接访问的特点取第K大/小元素即可。

该策略时间复杂度O(NlogN)。

思路二:维护一个大小为K的乱序数组进行替换

我们可以先取下来随机序列前K个元素,放在一个乱序的数组中,每次都换掉其中最小/大的元素(前提是比这个元素大/小)。
这样就不需要完整地排序,时间复杂度O(N*K)。
当K>logN的时候就是血亏,还不如直接排序呢。

思路三:维护一个堆最后直接取堆顶元素

我们上面的算法时间复杂度之所以需要×K,是因为乱序,每次要找最小/大元素,那我们不如维护一个大小为K的堆。求第K大就建立小根堆,最后直接取堆顶;求第K小就建立大根堆,最后也是直接取堆顶。

具体操作就是先拿前K个元素建立二叉小根堆/大根堆,需要时间O(K),然后每次调整也就是O(logK),次数是(N-K),所以是O(K+logK(N-K)),如果说K<<N,就近似于O(NlogK),这就能实现很大程度上的优化了。

思路四:分治法

这个思路很秀,简单说一下。
怎么分治呢?大家还记得快排吧,其实很相似,我们利用了与其“划分”很类似的做法。
以第K小元素为例:
取一个中间元素放到最左边,比其小的换到左边,比其大的不动,完成划分,不必排序。
划分完成就看看选取的中间值的id与K的关系,最后当K与id吻合时,左边的元素就是比K小的元素,划分中值就是第K小元素。
该算法的时间复杂度甚至能达到O(N),是不是很玄学?红红火火恍恍惚惚哈哈哈哈哈……

第K小元素-思路一-Java编程实现

其实我连快排都没用,用了TreeSet

第K大元素-思路三-Java编程实现

import java.util.Scanner;

public class Main {
    /**
     * 寻找第k大的元素
     * @param array  待调整的堆
     * @param k  第几大
     */
    public static int findNumberK(int[] array, int k){
        //用前k个元素构建小顶堆
        buildHeap(array, k);
        //继续遍历数组,和堆顶比较
        for (int i=k; i<array.length;i++){
            if (array[i] > array[0]){
                array[0] = array[i];
                downAdjust(array, 0, k);
            }
        }
        //返回堆顶元素
        return array[0];
    }

    /**
     * 构建堆
     * @param array  待调整的堆
     * @param length  堆的有效大小
     */
    private static void buildHeap(int[] array, int length) {
        //从最后一个非叶子节点开始,依次下沉调整
        for (int i = (length- 2)/ 2; i >= 0; i--) {
            downAdjust(array, i, length);
        }
    }

    /**
     * 下沉调整
     * @param array     待调整的堆
     * @param index    要下沉的节点
     * @param length    堆的有效大小
     */
    private static void downAdjust(int[] array, int index, int length) {
        //temp保存父节点值,用于最后的赋值
        int temp = array[index];
        int childIndex = 2 * index + 1;
        while (childIndex < length) {
            //如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
                childIndex++;
            }
            //如果父节点小于任何一个孩子的值,直接跳出
            if (temp <= array[childIndex])
                break;
            //无需真正交换,单向赋值即可
            array[index] = array[childIndex];
            index = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[index] = temp;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int num = scanner.nextInt(), k = scanner.nextInt();
        int[] array = new int[num];
        for (int i = 0; i < num; i++) {
            array[i] = scanner.nextInt();
        }
        scanner.close();
        System.out.println(findNumberK(array, k));
    }

}
发布了538 篇原创文章 · 获赞 1098 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/weixin_43896318/article/details/104369975
今日推荐