• Java数据结构 | PriorityQueue详解


    目录

    一 、PriorityQueue

    二、PriorityQueue常用方法介绍

    三、 PriorityQueue源码剖析

    四:应用:Top-K问题


    一 、PriorityQueue

    • 常用接口介绍

    上文中我们介绍了优先级队列的模拟实现, Java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,此处我们主要刨析和介绍PriorityQueue

    4e7c1a84bcf28502efc4d7df77f2290e.png

    关于PriorityQueue的使用要注意:

    1. 使用时必须导入PriorityQueue所在的包,即:

    2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出

    ClassCastException*异常**

    1. 不能插入null对象,否则会抛出NullPointerException**

    2. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

    3. 插入和删除元素的时间复杂度为

    4. PriorityQueue底层使用了堆数据结构, (注意:此处大家可以不用管什么是堆,后文中有介绍)

    5. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素、

    二、PriorityQueue常用方法介绍

    • 构造方法

    常用的三个构造方法如下:

    1d28a212dc43c529e770b656a1095cec.png

    1. public class TestDemo2 {
    2.    public static void main(String args[]){
    3. //     创建一个空的优先级队列,底层容量默认为11
    4.        PriorityQueue priorityQueue = new PriorityQueue<>();
    5.        priorityQueue.offer(10);
    6.        priorityQueue.offer(20);
    7.        priorityQueue.offer(12);
    8.        priorityQueue.offer(23);
    9.        System.out.println(priorityQueue.poll());
    10.        System.out.println(priorityQueue.poll());
    11.        System.out.println(priorityQueue.poll());
    12.        System.out.println(priorityQueue.poll());
    13. //       创建一个指定初始容量的优先级队列,容量指定位100
    14.        PriorityQueue priorityQueue1 = new PriorityQueue<>(100);
    15. //       使用ArrayList对象来创建一个优先级队列的对象(只要实现Collection接口的,都可以存入)
    16.        List list = new ArrayList<>();
    17.        list.add(10);
    18.        list.add(20);
    19.        list.add(32);
    20.        PriorityQueue priorityQueue2 = new PriorityQueue<>(list);
    21.        System.out.println(priorityQueue2.poll());
    22.        System.out.println(priorityQueue2.poll());
    23.   }
    24. }

    注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器

    e049e3bdcf9b2f56d39dad1b8b43ab9c.png

    三、 PriorityQueue源码剖析

    • 使用Student对象来创建一个优先级队列的对象

    当我们在priorityQueue中存放一个Student 对象时, 可以正常放入且不发生报错。

    但是当我们存放两个Studnet对象时,程序报错,出现类型不兼容异常。

    1. public class TestDemo2 {
    2. //   注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
    3.    public static void main(String[] args) {
    4.        PriorityQueue priorityQueue = new PriorityQueue<>();
    5.        priorityQueue.offer(new Student(10));
    6.        priorityQueue.offer(new Student(5));
    7.   }
    8. }

    0fd6bc3220869115d5a5f642df12c894.png

    前边学习抽象类和常用接口时,我们了解到Java中对于引用数据类型的比较或者排序,一般都要用到使用Comparable接口中的compareTo() 方法

    此时我们可以实现Comparable接口,并且重写 compare()方法。

    1. class Student implements Comparable{
    2.    public int age;
    3.    public Student(int age) {
    4.        this.age = age;
    5.   }
    6.    @Override
    7.    public int compareTo(Student o) {
    8.        return this.age - o.age;
    9.   }
    10. }
    11. public class TestDemo2 {
    12. //   注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
    13.    public static void main(String[] args) {
    14.        PriorityQueue priorityQueue = new PriorityQueue<>();
    15.        priorityQueue.offer(new Student(10));
    16.        priorityQueue.offer(new Student(5));
    17.   }
    18. }

    经过调试我们可以发现,此时优先级队列中的两个元素已经按照小根堆的方式调整好了。

    0653d9fc42d8553548acd28d50532eff.png

    那么PriorityQueue是怎么对其中的引用数据类型进行调整的呢?

    2d3e2a7b2e1e046a782af4fedd17603b.png

    使用this引用指向了下边的方法,并传递参数。

    0f5d46ef235c34074818ef17e04e6860.png

    当queue数组初始化完毕时, 需要向数组中存放元素,即进行 priorityQueue.offer(new Student(10));

    a357ae03bff5a5a7d9daf181f1827bde.png

    存放第二个元素时,i = 1 , size = 2 ,则需要执行 siftUp(1 , e) ,对 元素进行向上调整为小根堆 。

    41bcef9cd1147433157d48a662ee4046.png

    向下调整的过程中,使用了我们所重写的compareTo()方法,然后判断e,key对应的age的值,进行交换,如果此处不需要交换,则直接将key放入queue[1] 中即可 , 此时,小根堆调整完成

    fa67bcbb6cda9b6154bf8058f573602b.png

    如果想要调整为大根堆的话,只需要修改Student类中的compareTo()方法即可

    1. class Student implements Comparable{
    2.   public int age;
    3.   public Student(int age) {
    4.       this.age = age;
    5.   }
    6.   @Override
    7.   public int compareTo(Student o) {
    8.       return o.age - this.age;
    9.   }
    10. }

    cdb19d71f069688b738c505600307695.png

    那么Integer类型的参数该如何修改为大根堆 呢? ,Integer类型已经重写了compareTo方法,但是已经写死了,默认为小根堆的实现方式,无法修改源码,此时,我们就应该 构造Comparator 比较器来实现。

    1. // 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
    2. class IntCmp implements Comparator{
    3. @Override
    4. public int compare(Integer o1, Integer o2) {
    5.         //return o2-o1;
    6.         return o2.compareTo(o1);
    7.   }
    8. }
    9. public class TestPriorityQueue {
    10.     public static void main(String[] args) {
    11.           PriorityQueue p = new PriorityQueue<>(new IntCmp());
    12.           p.offer(4);
    13.           p.offer(3);
    14.           p.offer(2);
    15.           p.offer(1);
    16.           p.offer(5);
    17.   }
    18. }

    ❗当传入比较器时,PriorityQueue会按照 比较器的方式进行 比较,与实现Comparable 接口的方法类似,此处不再赘述,元素进而被调整为大根堆。

    d87918ed651e195c24dd8e6cfffd5168.png

    91906c6bcaf27804ca71635b89eeda96.png

    ✅另一种写法 :

    1. public class TestPriorityQueue {
    2.     public static void main(String[] args) {
    3.     //匿名内部类,这里有一个类,实现了Comparator 这个接口,并重写了compare这个方法
    4.           PriorityQueue p = new PriorityQueue<>(new Comparator() {
    5.               @Override
    6.               public int compare(Integer o1, Integer o2) {
    7.                   return o2 - o1;
    8.               }
    9.           });
    10.   }
    11. }

    🔻PriorityQueue的扩容机制:

    c84109602dae041fefbc39e611c8a472.png

    优先级队列的扩容说明:

    • 如果容量小于64时,是按照约oldCapacity的2倍方式扩容的(2*OldCapacity+2)

    • 如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的

    • 如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容

    四:应用-Top-K问题

    对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

    1. 用数据集合中前K个元素来建堆

    前k个最大的元素,则建小堆

    前k个最小的元素,则建大堆

    1. 用剩余的**N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素**

    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

    2db7cb5bb7f400e0d12608cd8a9d6d4e.png

    在不使用Arrays.sorrt 的情况下,使用优先级队列,(忽略时间复杂度)可以这样写:

    1 . 先将数组全部放入堆中,堆会自动调整为小根堆。

    2 . 每次将堆顶元素弹出,堆调整之后,再继续弹出共k 个堆顶。

    1. class Solution{
    2.    public int[] smallestK(int[] arr,int k){
    3.        PriorityQueue pr = new PriorityQueue<>();
    4.        for(int i = 0 ;i < arr.length ;i++){
    5.            pr.offer(arr[i]);
    6.       }
    7.        int[] tmp = new int[k];
    8.        for(int i = 0 ;i < k ;i ++){
    9.            tmp[i] = pr.poll();
    10.       }
    11.        return tmp;
    12.   }
    13. }

    此时的时间复杂度为 O(n+klog(n)),那么如何调整可以使时间复杂度进一步优化呢?

    1.先将这组数据中的前K个数据建立为大根堆

    2. 从K+1个元素开始,每次和堆顶元素进行比较,如果i下标的元素小于堆顶元素,则进行出堆。

    区别:1 . 没有整体建堆(大小为K的堆) 2. 遍历剩下n-k 个元素,每个元素与堆顶元素比较。

    1. class Solution {
    2.     public int[] smallestK(int[] arr, int k) {
    3.         int[] vec = new int[k];
    4.         if (k == 0) {
    5.             return vec;
    6.         }
    7.         //传入比较器,按照大根堆调整
    8.         PriorityQueue queue = new PriorityQueue(new Comparator() {
    9.           public int compare(Integer num1, Integer num2) {
    10.                 return num2 - num1;
    11.             }
    12.         });
    13.         //存入K个 元素
    14.         for (int i = 0; i < k; ++i) {
    15.             queue.offer(arr[i]);
    16.         }
    17.         //比较堆顶元素与剩余n - k个元素的值的大小
    18.         //如果堆顶元素较大,则弹出堆顶,重新调整,元素入堆
    19.         for (int i = k; i < arr.length; ++i) {
    20.             if (queue.peek() > arr[i]) {
    21.                 queue.poll();
    22.                 queue.offer(arr[i]);
    23.             }
    24.         }
    25.         //将堆中元素存入数组中
    26.         for (int i = 0; i < k; ++i) {
    27.             vec[i] = queue.poll();
    28.         }
    29.         return vec;
    30.     }
    31. }

    此时已经可以求出前K个最小的元素,且时间复杂度为Nlog(K)那么第K小的元素如何去求呢?步骤基本是相似的

    1.先将这组数据中的前K个数据建立为大根堆

    2. 从K+1个元素开始,每次和堆顶元素进行比较,如果i下标的元素小于堆顶元素,则进行出堆。

    3 . 比较完成后直接弹出堆顶元素,即为第K小的元素。

        到这里优先级队列部分的内容就结束了,欢迎点赞评论收藏。。💖

    ced485cbb11e458d81a746890b32cf3f.gif

     

  • 相关阅读:
    RK3568平台开发系列讲解(音频篇)Android AudioRecord 采集音频
    iOS开发UI模块分类和Mac电脑使用快捷键
    什么是VR应急预案演练虚拟化|VR体验馆加盟|元宇宙文旅
    Android APK 反编译+修改+重打包+签名
    单元测试pytest
    微信小程序本地生活(列表页面)
    一份 Python 日志配置,同时适用于开发和生产环境
    alibaba数据同步组件canal的实践整理
    JColorChooser 和JFileChooser
    ES6 新增功能复盘梳理
  • 原文地址:https://blog.csdn.net/m0_56361048/article/details/127950384