数据结构与算法--最小的k个数

本文探讨了寻找数组中最小k个数的不同方法,包括快速排序优化、最小堆和最大堆在海量数据场景的应用。从直接排序到基于快速排序的改进,再到利用最小堆和最大堆的空间效率优化,展示了在不同场景下的时间与空间复杂度分析。
摘要由CSDN通过智能技术生成
最小的k个数
  • 题目:输入n个整数,找出其中最小的k个数,例如输入4,5,6,7,8,9这六个数字,则最小的4个是4,5,6,7
方案一
  • 还是最直观的方法,先排序,最快的是快排O(nlog2n),然后遍历前面k个数组,得到我们需要的答案,这个最简单方案应该有更优实现
方案二
  • 题意,我们需要找出最小的k个数字,还是从快排的思路收到启发
  • 我们同样基于快速排序,但是只找出第k个大的数字
  • 因为快排过程中会将比基准值小的放到 之前,比基准值大的放到后面,因此只需要找到第k个大的数字,在之前的数就是我们需要的数
  • 基于如上分析有如下代码:
/**
 * 找出数组中最小的K个数
 *
 * @author liaojiamin
 * @Date:Created in 10:18 2021/6/16
 */
public class GetLeastNumbers {

    public static void main(String[] args) {
        Integer[] arrayData = new Integer[20];
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            arrayData[i] = random.nextInt(50);
            System.out.print(arrayData[i]+",");
        }
        System.out.println();
        Integer[] newArray = getLeastNumbers(arrayData, 10);
        for (Integer integer : newArray) {
            System.out.print(integer+",");
        }
    }

    /**
     * 按快速排序思路,找到第k个大的数,将小的放k前面
     * 将大的放k后,得到数组中最小的k个数就是下标是0~k的所有数
     */
    public static Integer[] getLeastNumbers(Integer[] array, Integer key) {
        if (array == null || array.length <= 0 || array.length < key) {
            return new Integer[]{};
        }
        if (array.length == key) {
            return array;
        }
        return quickSort(array, 0, array.length-1, key);

    }

    public static Integer[] quickSort(Integer[] array, Integer left, Integer right, Integer key) {
        if (left < right) {
            Integer middle = quickSortSwap(array, left, right);
            if (middle == key) {
                return Arrays.copyOfRange(array, 0, key);
            }
            quickSort(array, left, middle - 1, key);
            quickSort(array, middle + 1, right, key);
        }
        return Arrays.copyOfRange(array, 0, key);
    }

    public static Integer quickSortSwap(Integer[] array, Integer left, Integer right) {
        if (left < right) {
            Integer position = array[left];
            while (left < right) {
                while (left < right && array[right] > position) {
                    right--;
                }
                if (left < right) {
                    array[left] = array[right];
                    left++;
                }
                while (left < right && array[left] < position) {
                    left++;
                }
                if(left < right){
                    array[right] = array[left];
                    right --;
                }
            }
            array[left] = position;
        }
        return left;
    }
}

  • 基于快排的方案是有限制的,因为我们需要修改输入的数组,最后的顺序是变化的,如果要求不能修改输入的参数,我们是否有其他方案。
方案三
  • 既然不能修改原值,那么我们复制一个我们需要的值,还是空间换时间的做法
  • 我们建一个原数组大小的容器用来存储,接着在容器中找出最小的k个数
  • 本次我们需要的存储的特点是能快速的找到最小值,这样重复查找k次最小值,就能得到结果
  • 如果我们用二叉查找树来实现这个容器,那么我们每次查询的时间复杂度是O(logn),也就是层高度,那么k次查询就是O(klogn)
  • 但是还有其他变种的二叉查找树二叉堆中对小堆,之前文章:数据结构与算法–二叉堆(最大堆,最小堆)实现及原理对最大堆,最小堆的实现有详细的解释
  • 最小堆的特点在于能在O(1)时间内找到最小值,就是二叉堆的根节点
  • 并且二叉堆的结构特性就在于能够快速的查询,我们将所有数据构造成一个最小堆,然后经k次O(1)的操作,就能得到结果
  • 经如上分析有如下代码:
/**
 * 找出数组中最小的K个数
 *
 * @author liaojiamin
 * @Date:Created in 10:18 2021/6/16
 */
public class GetLeastNumbers {

    public static void main(String[] args) {
        Integer[] arrayData = new Integer[20];
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            arrayData[i] = random.nextInt(50);
            System.out.print(arrayData[i]+",");
        }
        System.out.println();
        Integer[] heapArray = getLeastNumbersByBinaryHeapMax(arrayData, 10);
        for (int i = 0; i < heapArray.length; i++) {
            System.out.print(heapArray[i]+",");
        }
    }

    /**
     * 利用二叉堆,最小堆的结构特性,构建最小堆后,每次去跟节点都是最小的节点
     * 循环取k个最小堆中根元素,得到我们的结果
     * */
    public static Integer[] getLeastNumbersByBinaryHeapMax(Integer[] array, Integer key){
        if (array == null || array.length <= 0 || array.length < key) {
            return new Integer[]{};
        }
        if (array.length == key) {
            return array;
        }
        Integer size = (array.length + 2 )*11/10;
        BinaryHeap binaryHeap = new BinaryHeap(size);
        for (int i = 0; i < array.length; i++) {
            binaryHeap.insert(new AnyType(array[i]));
        }
        Integer[] result = new Integer[key];
        for (Integer i = 0; i < key; i++) {
            result[i] = Integer.valueOf(binaryHeap.deleteMin().getElement().toString());
        }
        return result;

    }
}

方案四
  • 如上最小堆的实现中虽然找最小值都是O(1),但是在构造最小堆的过程中我们需要O(logn)的时间复杂度,如果题目要是海量数据,其实我们也可以用最大堆
  • 我们可以用k个元素的最大堆,当堆满后,每次读入原数组中一个数据,与最大数据比较(O(1)时间)
  • 如果比最大数据要小,我们删除最大数据,插入当前值,直到整个数组遍历完
  • 此时得到的最大堆中k个数据就是我们需要的数据
  • 这种方案可以用来处理海量数据时候内存占用过多的问题。
  • 海量数据情况下,空间复杂度O(k)的实现方式如下:
/**
 * 找出数组中最小的K个数
 *
 * @author liaojiamin
 * @Date:Created in 10:18 2021/6/16
 */
public class GetLeastNumbers {

    public static void main(String[] args) {
        Integer[] arrayData = new Integer[20];
        Random random = new Random();
        for (int i = 0; i < 20; i++) {
            arrayData[i] = random.nextInt(50);
            System.out.print(arrayData[i]+",");
        }
        System.out.println();
        Integer[] maxArray = getLeastNumberByBinaryHeapMax(arrayData, 10);
        for (Integer integer : maxArray) {
            System.out.print(integer+",");
        }
    }

    /**
     * 利用二叉堆,最大堆,处理海量数据情况下获取前k个最小的数据
     * */
    public static Integer[] getLeastNumberByBinaryHeapMax(Integer[] array, Integer key){
        if (array == null || array.length <= 0 || array.length < key) {
            return new Integer[]{};
        }
        if (array.length == key) {
            return array;
        }
        Integer size = (array.length+2)*11/10;
        BinaryHeapMax binaryHeapMax = new BinaryHeapMax(size);
        for (int i = 0; i < array.length; i++) {
            AnyType anyType = new AnyType(array[i]);
            if(binaryHeapMax.heapSize() >= key){
                Integer heapMax =  Integer.valueOf(binaryHeapMax.findMax().getElement().toString());
                if(array[i] < heapMax){
                    binaryHeapMax.deleteMax();
                    binaryHeapMax.insert(anyType);
                }
            }else {
                binaryHeapMax.insert(anyType);
            }

        }
        AnyType[] anyTypes = binaryHeapMax.getAppHeapData();
        Integer[] result = new Integer[key];
        for (int i = 0; i < anyTypes.length; i++) {
            result[i] = Integer.valueOf(anyTypes[i].getElement().toString());
        }
        return result;
    }
}

解法对比
  • 第一种方案直接排序遍历平均时间复杂度是O(nlog2n),比第二种思路上更容易理解,但是同时也有明显的限制会修改入参数组
  • 第二种解法,也是基于快排思路,但是可以在中间退出,因此时间复杂度小于O(nlog2n),同样也会修改入参数组
  • 第三种方法,最小堆的方式,先构建最小堆,在读取,这种有两个明显优点,一个是没有修改输入的数据,因为我们只是读取入参数组中的数字,所有写操作都是在最小堆中完成,二是解法简单,缺点也明显,时间复杂度更大,构建时候需要insert,n次,每次insert的平均时间复杂度是O(logn),因此是O(nlogn)
  • 第四种算法适合海量数据的输入,如果题目要求的是海量数数据中找k个数,内存的大小限制,不可能全读取如内存,这时候,我们只一次读取一个数据进内存,只要求内存容纳的下最大堆中的k个数据即可,能有效解决n很大,k很小的情况,时间复杂度也是O(nlogk)

上一篇:数据结构与算法–两个链表中第一个公共节点
下一篇:数据结构与算法–数字在排序数组中出现次数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值