当前位置: 首页 > article >正文

Java-数据结构-优先级队列(堆的使用)

在学习优先级队列的使用之前,大家最好先学习下这篇文章:Java-数据结构-优先级队列(堆)-CSDN博客
以便更好的理解优先级队列是什么,以及(堆)的底层究竟如何运作。

一、优先级队列

在我们之前学习的队列中,仅仅按照入队的先后顺序来进行排序,并没有特殊的优先级,这样的情况就像是一家正常运作的医院中,病人们在病人窗口先后排序

而在有些情况下,我们希望数据能够拥有一定的优先级,使我们每次取到的第一个元素都应该是当前所有数据中,优先级最高的那一个,这就像是繁忙的医院中,决定抢先医治病情严重,生命垂危的人:

而为了实现这种常规队列无法实现的要求,我们就引入了一个新的数据结构:优先级队列

① 优先级队列的特性

Java的集合框架中给出了两种类型的优先级队列,它们分别是:

"PriorityQueue"和"PriorityBlockingQueue"

两者的区别在于:前者线性不安全,后者线性安全,但相应的,后者效率必定不如前者高。

📚 PriorityQueue:

📕 适用于单线程环境,因为不需要处理线程同步,所以性能更高。

📕 适合单线程或手动管理线程同步的场景。

📚 PriorityBlockingQueue:

📕 适用于多线程环境,但线程同步会带来额外开销。

📕 适合多线程并发场景,能够简化线程安全的手动管理。

综上所述,两者的使用还需要看具体场景的需要,而由于我们刚刚接触优先级队列,所以这次就先为大家讲解"PriorityQueue"~

📖 优先级队列(PriorityQueue)的特性

📕 在优先级队列中放置的元素必须是能够比较大小的,如果向优先级队列中放置无法比较大小的元素,则会抛出异常:

📕 优先级队列中不能存放null

📕 PriorityQueue的内部会自动扩容

📕 PriorityQueue插入元素和删除元素的时间复杂度都为O(log n)

📕 PriorityQueue的底层使用的是,并且默认是小根堆

② 优先级队列的构造

📚 优先级队列的构造方法,常用的有以下几种

📕 PriorityQueue()

优先级队列的空参构造,创建一个空的优先级队列

通过这种方式去构造优先级队列,其默认的大小为11

📕 PriorityQueue(int initialCapacity)

优先级队列的带参构造创建一个初始容量为"initialCapacity"的优先级队列:

📕 PriorityQueue(Collection <?extend E>c)

用一个集合来创建优先级队列

而在上面我们所提到的一些问题,在这里我们也可以相应的去解决一下:

📕 在优先级队列中放置的元素必须是能够比较大小的:

比如有些时候,我们想要创建一个学生类,并且想要按照学生的学号来判定优先级(学号越小,优先级越高)并存入优先级队列中直接将学生类存入PriorityQueue中是断然不可以的,因为直接将学生类存入优先级队列中,学生类的比较方法没有重写,所以算是无法排序的元素。相应的也就会报错。

那么想要解决这个问题也是很简单,我们只需要重写相应的排序方法即可:

为了防止有些小伙伴没有看上一篇博客,而是直接学习这篇博客,所以这边我们再强调一下,需要注意的是,PriorityQueue底层是堆,所以并不是从头到尾都是线性按照大小关系排序的,而是通过一颗树,并且每棵子树的根节点优先级都高于它的两个子结点,形如:

📕 PriorityQueue的底层使用的是,并且默认是小根堆:

对于这种情况,其实就更好解决了,我们同样需要重写一个比较方法

③ 优先级队列的方法

以下仅列举一些常用的方法,其余内置的较为细致的方法,大家可以打开编译器自行阅读理解~

📖 offer方法 (插入元素)

📕 在队列中放入一个元素,如果放入成功则返回true,如果放入失败(满)则返回false;(但PriorityQueue是基于堆的动态扩容实现,不会满,因此总是返回 true)

与 offer 作用基本相同的方法还有一个我们熟知的 add,两者基本没有什么区别,两者唯一的区别就是(add在满时会直接抛出异常,而不是像offer一样优雅的返回false)

📖 peek方法 (获取优先级最高的元素)

📕 一般情况下,我们使用优先级队列的目的就是为了取出指定个数,优先级最高的元素。所以这个方法也是非常重要的,在之前我们学习中也已经见过,所以这里就不过多赘述了

📖 poll方法 (获取并移除优先级最高的元素)

📕 与上述方法基本一致~不多解释

📖 size方法 (获取有效元素个数)

📖 clear方法 (清空队列)

📖 isEmpty方法 (返回是(true)否(false)为空)

二、堆的应用

① 堆排序

我们使用堆可以实现很多功能,比如我们时常使用的排序中,就有可以用堆实现的排序:堆排序

具体思路还是比较简单的,我们知道堆的第一个元素是"优先级最高"的元素,那么如果我们使用大根堆,不断将堆顶的元素放到最后并删除(不是真删除,只是后续计算中不再移动这个元素),这样就能够做到最大的元素在最后,也就是得到一个升序排列的一段数据~

图示

📖 代码示例(1.PriorityQueue):

    public static void HeapSort(int[] arr){
        PriorityQueue<Integer> queue = new PriorityQueue<>(new ImpCmp());
        for (int i = 0; i < arr.length; i++) {
            queue.add(arr[i]);
        }
        for(int i = 0;i < arr.length;i++) {
            arr[i] = queue.poll();
        }
    }

📖 代码示例(2.模拟实现):

    public static void heapSort(int[] array){
        //构造一个大根堆
        createHeap(array);
        int len = array.length;
        for(int i = len - 1;i > 0;i--){
            //将array[0]放到最后(最大的数放到最后)
            swap(array,0,i);
            //向下调整,再次变成大根堆(堆到后面的大数不再调整)
            siftDown(array,0,i);
        }
    }
    public static void createHeap(int[] array){
        int index = array.length;
        //从最后一个父结点开始,依次将父结点向前进行向下调整
        for(int parent = (index - 1 - 1) / 2;parent >= 0;parent--){
            siftDown(array,parent,index);
        }
    }
    public static void siftDown(int[] array,int parent,int len){
        int child = 2 * parent + 1;
        while(child < len){
            if(child + 1 < len && array[child + 1] > array[child]){
                child = child + 1;
            }
            if(array[child] > array[parent]){
                swap(array,child,parent);
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }
    private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

② Top-k问题

对于这题得解法,就有很多种使用堆来解决的方法~

📕 法1(1). 模拟小根堆,排序取前k个元素

📖 代码示例

class Solution {
    public static int[] smallestK(int[] array,int k){
        createHeap(array);
        int len = array.length;
        for(int i = len - 1;i > 0;i--){
            swap(array,0,i);
            siftDown(array,0,i);
        }
        array = Arrays.copyOf(array,k);
        return array;
    }
    public static void createHeap(int[] array){
        int len = array.length;
        int parent = (len - 1 - 1) / 2;
        for(int i = parent;i >= 0;i--){
            siftDown(array,i,len);
        }
    }
    public static void siftDown(int[] array,int parent,int end){
        int child = parent * 2 + 1;
        while(child < end){
            if(child + 1 < end && array[child] < array[child + 1]){
                child++;
            }
            if(array[child] > array[parent]){
                swap(array,child,parent);
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }
    public static void swap(int[] array,int i,int j){
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
}

📕 法1(2). 直接创建小根堆,排序取前k个元素

📖 代码示例

class Solution {
    public static int[] smallestK(int[] array, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int i = 0; i < array.length; i++) {
            queue.offer(array[i]);
        }
        int[] arr = new int[k];
        for (int i = 0; i < k; i++) {
            arr[i] = queue.poll();
        }
        return arr;
    }
}

📕 法2.重写compare,创建大小为k的大根堆

不断将小于堆顶的元素与堆顶交换,最后剩余的大根堆就是我们要找的前k个最小元素

📖 代码示例

class IntBig implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}

class Solution {
    public static int[] smallestK(int[] array, int k) {
        if (k == 0 || array.length == 0) {
            return new int[0];
        }
        // 创建大根堆
        PriorityQueue<Integer> queue = new PriorityQueue<>(new IntBig());
        int[] arr = new int[k];

        for (int i = 0; i < k; i++) {
            queue.offer(array[i]);
        }
        for(int i = k;i < array.length;i++){
            if(array[i] < queue.peek()){
                queue.poll();
                queue.offer(array[i]);
            }
        }
        for (int i = k - 1; i >= 0; i--) {
            arr[i] = queue.poll();
        }
        return arr;
    }
}

📕 法3.Top-k方法

建立一个大根堆,通过给定的k,将数组前k个元素一一放进堆中,然后遍历剩余的元素,如果出现元素小于当前的堆顶元素,则将堆顶元素删除,并将这个小于堆顶元素的元素放入堆中(并重新排序),直到将所有元素遍历完,最后得到的就是前k个最小元素。

📖 代码示例

class IntBig implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}
class Solution {
    public int[] smallestK(int[] array, int k) {
        int[] arr = new int[k];
        if(k == 0){
            return arr;
        }
        PriorityQueue<Integer> queue = new PriorityQueue<>(new IntBig());
        for(int i = 0;i < k;i++){
            queue.offer(array[i]);
        }
        for(int i = k;i < array.length;i++){
            if(array[i] < queue.peek()){
                queue.poll();
                queue.offer(array[i]);
            }
        }
        for(int i = k - 1;i >= 0;i--){
            arr[i] = queue.poll();
        }
        return arr;
    }
}

这篇文章我们对"优先级队列"进行了进一步的了解,那么这篇文章到这里就结束啦,作者能力有限,如果有哪里说的不够清楚或者不够准确,还请各位在评论区多多指出,我也会虚心学习的,我们下次再见啦


http://www.kler.cn/a/532944.html

相关文章:

  • 安全实验作业
  • Leetcode - 周赛434
  • TensorFlow 与 PyTorch 的直观区别
  • 实战:如何利用网站外部链接提升收录?
  • Linux:文件系统(软硬链接)
  • 【C语言设计模式学习笔记1】面向接口编程/简单工厂模式/多态
  • 数据中心服务器对PCIe测试的需求、挑战和应用
  • 【大数据技术】本机DataGrip远程连接虚拟机MySQL/Hive
  • 5分钟掌握React的Redux Toolkit + Redux
  • 深度学习篇---张量数据流动处理
  • windows环境下如何在PyCharm中安装软件包
  • 【CSS】什么是响应式设计?响应式设计的基本原理,怎么做
  • 实际操作 检测缺陷刀片
  • 【自学嵌入式(8)天气时钟:天气模块开发、主函数编写】
  • 新手STM32:基于HAL库的定时器和PWM输出
  • 利用Docker简化机器学习应用程序的部署和可扩展性
  • 项目中常用中间件有哪些?分别起什么作用?
  • (10) 如何获取 linux 系统上的 TCP 、 UDP 套接字的收发缓存的默认大小,以及代码范例
  • Mac M1 ComfyUI 中 AnyText插件安装问题汇总?
  • Unity 2D实战小游戏开发跳跳鸟 - 计分逻辑开发
  • 1.PPT:天河二号介绍【12】
  • Vue - toRaw 与 markRaw
  • Kubeflow——K8S的机器学习利器
  • 人工智能基础知识速成 - 机器学习、深度学习算法原理及其实际应用案例
  • 2025年最新Stable Diffusion 新手入门教程,安装使用及模型下载
  • 【鸿蒙HarmonyOS Next实战开发】Web组件H5界面与原生交互-抽奖页面