Java数据结构 | PriorityQueue详解

news2025/2/28 15:43:22

目录

一 、PriorityQueue

二、PriorityQueue常用方法介绍

三、 PriorityQueue源码剖析

四:应用:Top-K问题


一 、PriorityQueue

  • 常用接口介绍

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

关于PriorityQueue的使用要注意:

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

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

ClassCastException*异常**

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

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

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

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

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

二、PriorityQueue常用方法介绍

  • 构造方法

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

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

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

三、 PriorityQueue源码剖析

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class Student implements Comparable<Student>{
    public int age;
    public Student(int age) {
        this.age = age;
    }
    @Override
    public int compareTo(Student o) {
        return o.age - this.age;
    }
}

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

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

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

✅另一种写法 :

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

🔻PriorityQueue的扩容机制:

优先级队列的扩容说明:

  • 如果容量小于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个最小或者最大的元素。

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

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

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

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

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

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

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

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

class Solution {
     public int[] smallestK(int[] arr, int k) {
         int[] vec = new int[k];
         if (k == 0) {
             return vec;
         }
         //传入比较器,按照大根堆调整
         PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
            public int compare(Integer num1, Integer num2) {
                 return num2 - num1;
             }
         });
         //存入K个 元素
         for (int i = 0; i < k; ++i) {
             queue.offer(arr[i]);
         }
         //比较堆顶元素与剩余n - k个元素的值的大小
         //如果堆顶元素较大,则弹出堆顶,重新调整,元素入堆
         for (int i = k; i < arr.length; ++i) {
             if (queue.peek() > arr[i]) {
                 queue.poll();
                 queue.offer(arr[i]);
             }
         }
         //将堆中元素存入数组中
         for (int i = 0; i < k; ++i) {
             vec[i] = queue.poll();
         }
         return vec;
     }
 }

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

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

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

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

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

ced485cbb11e458d81a746890b32cf3f.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/21334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2021 XV6 4:traps

目录 1.RISC-V assenbly 2.Backtrace 3.Alarm 1.RISC-V assenbly 第一个任务是阅读理解&#xff0c;一共有6个问题。 1.Which registers contain arguments to functions? For example, which register holds 13 in mains call to printf? 具体来说就是a0&#xff0c;a1几个…

Docker入门

目录 Docker的作用 Docker的核心概念 Docker安装 镜像命令 镜像下载 查看镜像 搜索镜像 删除镜像 容器命令 创建容器 列出容器 新建并启动容器(最常使用) 守护态运行 启动容器 终止容器 重启容器 进入容器 attach命令 exec命令&#xff08;最常使用&#xff09; 退出容器…

【JavaEE】一文掌握 Ajax

&#x1f431;‍&#x1f3cd;目录1. AJAX 简介2. 伪造Ajax演示3. jQuery.ajax3.1 简单测试&#xff0c;使用最原始的HttpServletResponse处理3.2 使用ajax动态构建前端表格3.3 登录提示效果小demo4. 练习小demo&#xff0c;实现百度搜索框的动态内容提示5. 总结&#xff1a;1.…

纸牌游戏洗牌发牌排序算法设计

纸牌游戏洗牌发牌排序算法设计 本文提供纸牌游戏设计制作的基础部分&#xff0c;即洗牌&#xff0c;发牌&#xff0c;牌张排序排列显示的算法。 以及游戏开始时间使用时间的显示。我是用简单的C语言编译器MySpringC在安卓手机上编写的。此是游戏的框架&#xff0c;供游戏设计者…

计算机网络4小时速成:网络层,虚电路和数据包服务,ipv4,ABC类地址,地址解析协议ARP,子网掩码,路由选择协议,路由器

计算机网络4小时速成&#xff1a;网络层&#xff0c;虚电路和数据包服务&#xff0c;ipv4,ABC类地址&#xff0c;地址解析协议ARP&#xff0c;子网掩码&#xff0c;路由选择协议&#xff0c;路由器 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;…

关于瑞萨R7 的CANFD切换为经典CAN

首先,R7的CANFD是兼容CAN通讯的&#xff0c;在R7芯片他们公用相同的寄存器&#xff0c;至于发出来的帧是CANFD还是CAN取决于协议的不同。 CANFD是可变速率数据段为可变长度&#xff0c;扩展到64Byte&#xff0c;仲裁段和数据段的速率不相同。CANFD新增了FDF,BRS,ESI。FDF表示是…

牛客_小白月赛_61

传送门 A 如果不是特意防止溢出了&#xff0c;那么需要用long,否则会一直卡 很普通的写法,超了就 1, 最后补上一个 1就行 (所以, 这题我wa了8次, 卡了半个小时,就是因为没开 long ! ! !) package com.csh.A; /*** author :Changersh* date : 2022/11/18*/import java.io.*; i…

day02 springmvc

day02 springmvc 第一章 RESTFul风格交互方式 第一节 RESTFul概述 1. REST的概念 REST&#xff1a;Representational State Transfer&#xff0c;表现层资源状态转移。 定位&#xff1a;互联网软件架构风格倡导者&#xff1a;Roy Thomas Fielding文献&#xff1a;Roy Thom…

Android源码学习---init

init&#xff0c;是linux系统中用户空间的第一个进程&#xff0c;也是Android系统中用户空间的第一个进程。 位于/system/core/init目录下。 分析init int main(int argc, char **argv) { //设置子进程退出的信号处理函数 sigchld_handler act.sa_handler sigchld_handler;…

【博学谷学习记录】超强总结,用心分享丨人工智能 Python面向对象 学习总结之Python与Java的区别

目录前言简述面向对象类对象特性前言 经过学习&#xff0c;对Python面向对象部分有了一定的了解。 总结记录&#xff1a;面向对象上Python与Java的部分区别 简述 从类、对象、特性三个层面来简述其部分区别 面向对象 类 PythonJava定义class ClassName(object):passpubl…

2000-2020年各省固定资本存量数据

2000-2020年各省资本存量数据 1&#xff1a;来源&#xff1a;统计NJ、各省统计NJ 2、时间&#xff1a;2000-2020年 3、包括&#xff1a;30个省 4、数据说明&#xff1a;含原始数据和计算过程及最终结果 4、指标说明&#xff1a; 参考文献&#xff1a; 单豪杰&#xff08;…

【微服务架构组件之注册中心】注册中心选型-我只选nacos

注册中心的产生是基于用来解耦服务提供者(Provider)与消费者&#xff08;Consumer&#xff09;的关系&#xff0c;分布式设计架构下&#xff0c;众多的服务提供者的数量并不是动态不变的&#xff0c;在传统的静态LB的方案中&#xff0c;无法很好感知这种变化&#xff1b; 在分…

[附源码]java毕业设计网上宠物商店

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

进度条——不仅仅是语言层面上的小程序

文章目录\r和\n进度条完整代码\r和\n 在老式键盘上&#xff0c;回车键是这样的形状 但是该键的功能它不仅仅是回车&#xff0c;而是回车换行&#xff01; 这里需要明白两个概念&#xff1a; 回车&#xff1a;光标移动到当前行的行首 换行&#xff1a;光标移动到当前位置的…

跟艾文学编程《Python基础》Anaconda 安装

作者&#xff1a;艾文&#xff0c;计算机硕士学位&#xff0c;企业内训讲师和金牌面试官&#xff0c;公司资深算法专家&#xff0c;现就职BAT一线大厂。 邮箱&#xff1a;1121025745qq.com 博客&#xff1a;https://edu.csdn.net/lecturer/894?spm1003.2001.3001.4144 内容&am…

原生AJAX

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;微微的猪食小窝 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 微微的猪食小窝 原创 1、AJAX 简介 AJAX 全称为Asynchronous Javascript And XML,就是异步的JS 和 XML. 通过AJAX可以在浏览器中向服务器…

Vue3留言墙项目——主体部分静态、mock

文章目录主体头部主体关键部分小卡片组件创建mock数据以及使用主体头部 主体部分显示的内容&#xff0c;根据头部点击的是留言墙还是照片墙的按钮&#xff0c;显示不同的内容。 将照片墙和留言墙要渲染的数据抽取到一个js中&#xff0c;在导入的Main.vue&#xff08;主体页面&…

[go]汇编ASM简介

文章目录汇编(ASM)寄存器帧指针FP常见指令函数示例生成汇编Go汇编代码主要用于优化和与底层系统交互&#xff0c;并不会像其它的经典汇编代码那样独立运行。汇编(ASM) Go ASM是一种被Go编译器使用的特殊形式的汇编语言&#xff08;伪汇编&#xff09;&#xff0c;它基于Plan9输…

记录一次Powerjob踩的坑(Failed to deserialize message)

一. 问题描述: 在本地开发环境, server端和worker都运行正常. 但是发布到SIT环境(容器)的时候, 服务端却监测不到worker(worker可以找到服务端) 二. 问题表现: 1.服务端看不到Worker信息 2. 服务端日志信息 : Failed to deserialize message from [akka://oms111.111.111…

ECMAScript modules规范示例详解

引言 很多编程语言都有模块这一概念&#xff0c;JavaScript 也不例外&#xff0c;但在 ECMAScript 2015 规范发布之前&#xff0c;JavaScript 没有语言层面的模块语法。模块实际上是一种代码重用机制&#xff0c;要实现代码重用&#xff0c;将不同的功能划分到不同的文件中是必…