【数据结构】源码角度剖析PriorityQueue

目录

认识 Queue

认识 PriorityQueue

PriorityQueue为什么要用二叉堆?

PriorityQueue构造方法源码分析

PriorityQueue 的属性

构造方法

JDK1.8传入不可比较的对象

JDK17传入不可比较的对象

传入带有Collection接口的对象 

instanceof 关键字

Offer方法分析 

JDK8Offer分析(传入可比较对象)

JDK17Offer分析(传入可比较对象)

JDK17Offer(手动传入比较器)

PriorityQueue 扩容机制

模拟堆操作


认识 Queue

Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出 规则。

下面我们看看常见的方法:

Queue 扩展了 Collection 的接口,根据 因为容量问题而导致操作失败后处理方式的不同 可以分为两类方法: 一种在操作失败后会抛出异常,另一种则会返回特殊值。

Queue接口抛出异常返回特殊值
插入队尾add(E e)offer(E e)
删除队首remove()poll()
查询队首元素element()peek()

认识 PriorityQueue

PriorityQueue是在 JDK1.5 中被引入的, 其与 Queue 的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队。

这里列举其相关的一些要点:

  • PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  • PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
  • PriorityQueue 是非线程安全的,且不支持存储 NULL(否则会抛出NullPointerException) 和 non-comparable(否则会抛出 ClassCastException异常) 的对象。
  • PriorityQueue 默认是小堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后。

 

PriorityQueue为什么要用二叉堆?

PriorityQueue 选择使用二叉堆(Binary Heap)作为底层数据结构的原因主要是为了维护堆的性质,并保证一些基本操作的高效性能。二叉堆是一种特殊的完全二叉树,有两种类型:最小堆和最大堆。

以下是使用二叉堆的一些优势和原因:

  1. 高效的插入和删除操作: 二叉堆保持了堆的性质,使得插入和删除元素的操作非常高效。在最小堆中,堆顶元素是最小的,删除堆顶元素(poll操作)只需要常数时间,而插入元素也可以在对数时间内完成。

  2. 空间效率: 二叉堆可以使用数组实现,这样可以更有效地利用内存。相比于其他数据结构,它在实现上不需要额外的指针,而且数组的顺序存储有助于提高缓存命中率。

  3. 快速找到最小/最大元素: 在最小堆中,最小元素总是位于堆顶。在最大堆中,最大元素总是位于堆顶。这样,我们可以在常数时间内找到最小或最大元素。

  4. 适用于动态数据: 二叉堆对于动态数据集的管理非常有效,因为在插入和删除元素时,它能够在对数时间内维护堆的性质。

虽然二叉堆可能不是最适合所有情况的数据结构,但在某些场景下,特别是对于优先队列的实现,它提供了一种简单而高效的选择。注意,Java 中的 PriorityQueue 默认实现是最小堆,但可以通过提供自定义的比较器来实现最大堆。

PriorityQueue构造方法源码分析

就第三点:PriorityQueue 不支持存储 NULL(否则会抛出NullPointerException) 和 non-comparable(否则会抛出 ff异常) 的对象。

我们来看看源码:

PriorityQueue 的属性

下面为 PriorityQueue 的属性:(如想了解源码中的序列化和modCount相关知识,可移步博主另一篇博客ArrayList(源码分析))

    // 默认初始化大小private static final int DEFAULT_INITIAL_CAPACITY = 11;// 用数组实现的二叉堆,下面的英文注释确认了我们前面的说法。/*** Priority queue represented as a balanced binary heap: the two* children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The* priority queue is ordered by comparator, or by the elements'* natural ordering, if comparator is null: For each node n in the* heap and each descendant d of n, n <= d.  The element with the* lowest value is in queue[0], assuming the queue is nonempty.*/private transient Object[] queue ;// 队列的元素数量private int size = 0;// 比较器private final Comparator<? super E> comparator;// 修改版本private transient int modCount = 0;

构造方法

/*** 默认构造方法,使用默认的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口*/public PriorityQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}/*** 使用指定的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口*/public PriorityQueue( int initialCapacity) {this(initialCapacity, null);}/*** 使用指定的初始大小和比较器来构造一个优先队列*/public PriorityQueue( int initialCapacity,Comparator<? super E> comparator) {// Note: This restriction of at least one is not actually needed,// but continues for 1.5 compatibility// 初始大小不允许小于1if (initialCapacity < 1)throw new IllegalArgumentException();// 使用指定初始大小创建数组this.queue = new Object[initialCapacity];// 初始化比较器this.comparator = comparator;}/*** 构造一个指定Collection集合参数的优先队列*/public PriorityQueue(Collection<? extends E> c) {// 从集合c中初始化数据到队列initFromCollection(c);// 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparatorif (c instanceof SortedSet)comparator = (Comparator<? super E>)((SortedSet<? extends E>)c).comparator();else if (c instanceof PriorityQueue)comparator = (Comparator<? super E>)((PriorityQueue<? extends E>)c).comparator();//  如果集合c没有包含比较器,则默认比较器Comparator为空else {comparator = null;// 调用heapify方法重新将数据调整为一个二叉堆heapify();}}/*** 构造一个指定PriorityQueue参数的优先队列*/public PriorityQueue(PriorityQueue<? extends E> c) {comparator = (Comparator<? super E>)c.comparator();initFromCollection(c);}/*** 构造一个指定SortedSet参数的优先队列*/public PriorityQueue(SortedSet<? extends E> c) {comparator = (Comparator<? super E>)c.comparator();initFromCollection(c);}/*** 从集合中初始化数据到队列*/private void initFromCollection(Collection<? extends E> c) {// 将集合Collection转换为数组aObject[] a = c.toArray();// If c.toArray incorrectly doesn't return Object[], copy it.// 如果转换后的数组a类型不是Object数组,则转换为Object数组if (a.getClass() != Object[].class)a = Arrays. copyOf(a, a.length, Object[]. class);// 将数组a赋值给队列的底层数组queuequeue = a;// 将队列的元素个数设置为数组a的长度size = a.length ;}

JDK1.8传入不可比较的对象

接下来我们先来看一组场景:在PriorityQueue中插入未提供排序方法的对象,以此来展示构造方法

JDK1.8:

:第一次放入一个不可比较的对象不会报错,第二次时候才报错,原因下面讲解offer源码时候会解释。

JDK17传入不可比较的对象

而JDK17(第一次放入一个不可比较的对象就立即报错):

如果将Student实现Comparable接口就不会报错(以下均为JDK17实现):

import java.util.PriorityQueue;
class Student implements Comparable<Student>{public int age;public Student(int age) {this.age = age;}@Overridepublic int compareTo(Student o) {return this.age - o.age;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> queue = new PriorityQueue<>();queue.offer(new Student(11));queue.offer(new Student(7));System.out.println(1);
//        queue.add(new Student(12));}
}

运行结果:排序成功

传入带有Collection接口的对象 

 

代码实现:

分析:ArrayList实现了Collection接口,所以可以直接进行排序(PriorityQueue 默认小根堆)。

instanceof 关键字

instance of 是 Java 中的一个关键字,用于检查对象是否是某个类的实例,或者是否实现了某个接口。它的语法是:

object instanceof Class

其中,object 是一个对象,而 Class 是一个类名或接口名。instanceof 的结果是一个布尔值,如果 object 是 Class 的实例或者实现了 Class 接口,则结果为 true;否则,结果为 false。

例如,考虑以下代码:

List<String> myList = new ArrayList<>();
if (myList instanceof ArrayList) {System.out.println("myList is an instance of ArrayList");
} 
if (myList instanceof List) {System.out.println("myList is an instance of List");
}

在这个例子中,myList是 ArrayList 类型的实例,所以第一个条件为 true,而第二个条件也为 true,因为 ArrayList 是 List 接口的实现。因此,会输出两行信息。 

Offer方法分析 

分析以上场景所用到的构造方法:

第一次offer:new Student(11);

当未给构造方法传入参数的时候,源码中会调用自身带有两个参数的构造方法,并设置数组的初始容量为11。

 带有两个参数的构造方法如下:

由于之前我们并未对构造方法传入比较器,所以这里的比较器为 null。 容量为初始容量:11。

讲到这里:代码中,PriorityQueue<Student> queue = new PriorityQueue<>();所作的任务已经完成。

JDK8Offer分析(传入可比较对象)

下面我们来看看offer源码的实现:

(JDK8中的offer),这也是为什么我们前面第一次放入未实现Comparable接口的Student对象未报错:

JDK17Offer分析(传入可比较对象)

JDK17源码:

无论是第几次放入元素,都会先进行类型转换(未传入比较器的情况下,如果传入对象未实现Compara接口,就会抛出异常——ClassCastException)。

下面我们重点关注 siftUpComparable 方法的内部实现:

    private static <T> void siftUpComparable(int k, T x, Object[] es) {Comparable<? super T> key = (Comparable<? super T>) x;while (k > 0) {int parent = (k - 1) >>> 1;Object e = es[parent];if (key.compareTo((T) e) >= 0)break;es[k] = e;k = parent;}es[k] = key;}

 图解分析:

可以看出JDK17,对类型检查更为严格。

第二次Offer操作:new Student(7)

以下为siftUpComparable的具体实现: 

看到这里,我们知道,就可以通过我们自定义的compareTo方法来实现排序的顺序。

但是,如果我们传入的是整数呢?我们知道在进行offer操作的时候,整数会自动装箱成为Integer类型,但是我们不可能修改Integer的源码吧?

public class Main {public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>();queue.offer(10);queue.offer(12);}
}

JDK17Offer(手动传入比较器)

因此这时候比较器就派上用场了:

class IntCom implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);}
}
public class Main {public static void main(String[] args) {PriorityQueue<Integer> queue = new PriorityQueue<>(new IntCom());queue.offer(19);queue.offer(12);}
}

接下来我们再来看看关于比较器的源码部分:

offer方法如下:

由于这里的siftUpUsingComparator部分与上面的传入带Comparable接口的对象差不多,这里就不作过多介绍。 

PriorityQueue 扩容机制

模拟堆操作

import java.util.Arrays;public class Heap_imitate {public int[] elem;public int usedSize;//有效的数据个数public static final int DEFAULT_SIZE = 10;public Heap_imitate() {elem = new int[DEFAULT_SIZE];}public void initElem(int[] array) {for (int i = 0; i < array.length; i++) {elem[i] = array[i];usedSize++;}}/*** 时间复杂度:O(n)*/public void createHeap() {for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {//统一的调整方案shiftDown(parent,usedSize);}}/**** @param parent 每棵子树的根节点* @param len  每棵子树调整的结束位置 不能>len*             时间复杂度:O(log n)*/private void shiftDown(int parent,int len) {int child = 2*parent+1;//1. 必须保证有左孩子while (child < len) {//child+1 < len && 保证有右孩子if(child+1 < len && elem[child] < elem[child+1]) {child++;}//child下标 一定是左右孩子 最大值的下标/* if(elem[child] < elem[child+1] && child+1 < len ) {child++;}*/if(elem[child] > elem[parent]) {int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;parent = child;child = 2*parent+1;}else {break;}}}public void offer(int val) {if(isFull()) {//扩容elem = Arrays.copyOf(this.elem,2*this.elem.length);}this.elem[usedSize] = val;usedSize++;//想办法让他再次变成大根堆shiftUp(usedSize-1);}private void shiftUp(int child) {int parent = (child-1) / 2;while (child > 0) {//parent >= 0if (elem[child] > elem[parent]) {int tmp = elem[child];elem[child] = elem[parent];elem[parent] = tmp;child = parent;parent = (child-1)/2;}else {break;}}}public boolean isFull() {return usedSize == elem.length;}public int pop() {if(isEmpty()) {return -1;}int tmp = elem[0];elem[0] = elem[usedSize-1];elem[usedSize-1] = tmp;usedSize--;//保证他仍然是一棵大根堆shiftDown(0,usedSize);return tmp;}public boolean isEmpty() {return usedSize == 0;}public int peek() {if(isEmpty()) {return -1;}return elem[0];}/*** 时间复杂度:*  O(n) + O(n*logn) 约等于 O(nlogn)*  空间复杂度:O(1)*/public void heapSort() {//1.建立大根堆 O(n)createHeap();//2.然后排序int end = usedSize-1;while (end > 0) {int tmp = elem[0];elem[0] = elem[end];elem[end] = tmp;shiftDown(0,end);end--;}}}

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

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

相关文章

.net core 连接数据库,通过数据库生成Modell

1、安装EF Core Power Tools&#xff1a;打开Vs开发工具→扩展→管理扩展 2、(切记执行这步之前确保自己的代码不存在编写或者编译错误&#xff01;)安装完成后在你需要创建数据库实体的项目文件夹上面单击右键&#xff0c;找到EF Core 工具&#xff08;必须安装扩展之和才会有…

(Ant X6)子组件里的流程图画布无法显示

(Ant X6)子组件里的流程图画布无法显示 问题背景&#xff1a;侧导航页面都是子组件,建模页面的画布无法显示 解决前&#xff1a; 解决后&#xff1a; 解决思路&#xff1a;点击建模菜单时再次加载对应组件 在 Vue 中&#xff0c;每个组件都有一个唯一的 key 属性。当组件的 ke…

vue升级题

不熟悉的&#xff1a; 2&#xff0c; 3.你用过befcoreDetory 吗&#xff1f;清除定时器&#xff0c;第一个和第二个再看一下 实例加载完成是在哪个生命周期--beforecreate 7.父子组件生命周期执行顺序&#xff1f;为什么这么渲染&#xff1f;场景 8.简单描述每个周期具体适…

第二十章总结。。。

20.1线程简介. 20.2创建线程 2.1继承Thread类 Thread 类是java.lang包中的一个类&#xff0c;从这个类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下: public Thread():创建一个新的线程对象。 public Thread…

中医诊所/中医馆如何打破发展局限,创新引流获客?

随着社会生活水平不断发展&#xff0c;人们对医疗卫生服务水平的要求也越来越精细化&#xff0c;多样化。中医诊所/中医馆&#xff0c;在与传统服务发展中正在面临着诸多挑战与难题。 传统宣传推广&#xff1a;医疗市场竞争激烈&#xff0c;许多中医诊所、中医馆仍依赖传统的口…

使用Java将properties转为yaml,保证顺序、实测无BUG版本

使用Java将properties转为yaml 一 前言1.1 顺序错乱的原因1.2 遗漏子节点的原因 二、优化措施三、源码 一 前言 浏览了一圈网上的版本&#xff0c;大多存在以下问题&#xff1a; 转换后顺序错乱遗漏子节点 基于此进行了优化&#xff0c;如果只是想直接转换&#xff0c;可直接…

React自定义Hook之useModel hook

一、概述 useModel hook是React Hook中一个自定义的钩子函数&#xff0c;用于管理应用程序中的状态和逻辑。它主要用于组件之间的状态共享和通信。 useModel hook通常包含以下几个步骤&#xff1a; 1.创建模型&#xff1a;定义需要共享的状态和相关的方法&#xff0c;可…

性能测试:系统架构性能优化

今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#x…

C++ CryptoPP使用AES加解密

Crypto (CryptoPP) 是一个用于密码学和加密的 C 库。它是一个开源项目&#xff0c;提供了大量的密码学算法和功能&#xff0c;包括对称加密、非对称加密、哈希函数、消息认证码 (MAC)、数字签名等。Crypto 的目标是提供高性能和可靠的密码学工具&#xff0c;以满足软件开发中对…

开启虾皮购物新旅程,快速注册买家号

想要在shopee上畅享丰富的购物体验吗&#xff1f;那就让我们一起迈出第一步&#xff0c;注册一个属于你自己的虾皮买家号吧&#xff01; 1. 访问虾皮平台 首先&#xff0c;打开你的浏览器&#xff0c;输入虾皮平台网址&#xff0c;点击注册或登录按钮。这将引导你进入注册界面…

企业微信http协议接口调用,根据手机号搜索联系人

产品说明 一、 hook版本&#xff1a;企业微信hook接口是指将企业微信的功能封装成dll&#xff0c;并提供简易的接口给程序调用。通过hook技术&#xff0c;可以在不修改企业微信客户端源代码的情况下&#xff0c;实现对企业微信客户端的功能进行扩展和定制化。企业微信hook接口…

香港媒体发稿:7个技巧助你走上营销巅峰-华媒舍

营销对于企业来说是非常重要的一环。在如今的竞争激烈的市场环境中&#xff0c;如何让自己的产品或服务更好地被消费者接受和认可&#xff0c;是每家企业都需要思考的问题。在这篇文章中&#xff0c;我们将介绍香港港媒体发稿的七个技巧&#xff0c;帮助你将营销推向一个新的高…

js 页面截图三种解决方案

1.html2canvas npm install html2canvas // 引入html2canvas库 import html2canvas from html2canvas;// 设置定时器&#xff0c;每隔10秒执行一次截图操作 setInterval(async () > {try {// 将网页内容转换为canvas元素const canvas await html2canvas(document.body);/…

Python武器库开发-前端篇之JavaScript基础语法(三十四)

前端篇之JavaScript基础语法(三十四) JavaScript的三种引用方式 JavaScript的三种引用方式分别是&#xff1a; 内部引用&#xff08;内联式&#xff09;&#xff1a;将JavaScript代码嵌入到HTML页面中的<script>标签内部。例如&#xff1a; <script type"tex…

通过lua脚本在redis中处理json数据

在日常开发中&#xff0c;系统都会使用redis作为缓存来加快服务的响应&#xff0c;我们通常会将一个对象数据存储在redis中&#xff0c;对象存储通常有两种方案&#xff1a;一种是存储为hash结构&#xff0c;对象的键是属性名&#xff0c;值为属性值&#xff1b;另一种是序列化…

【自动化测试】pytest 用例执行中print日志实时输出

author: jwensh date: 20231130 pycharm 中 pytest 用例执行中 print 日志 standout 实时命令行输出 使用场景 在进行 websocket 接口进行测试的时候&#xff0c;希望有一个 case 是一直执行并接受接口返回的数据 def on_message(ws, message):message json.loads(message)…

NAT网络地址转换

目录 什么是nat nat 实验如何使用SNAT 和 DNAT 实验环境 内网连接外网 1.给网关服务器添加网卡&#xff08;两张网卡&#xff09; 2.查看新添加的网卡名 编辑网卡配置 3.开启路由转发 4.打开内网服务器 5.切换到外网服务器&#xff08;192.168.17.30&#xff0…

JavaScript添加快捷键、取消浏览器默认的快捷操作、js查看键盘按钮keycode值

document.addEventListener("keydown",function (event) {// 如果不知道按键对应的数字&#xff08;keyCode&#xff09;是多少可以弹出查看一下// alert(event.keyCode)if (event.ctrlKey && event.altKey && event.view["0"] null){if(…

[PTP][1588v2] Follow_Up消息

一、报文格式 0------3--------7--------11--------15--------------------------------31 |TranSpec|MsgType|Reserved1| VerPTP | MsgLength | ----------------|------------------|---------------------------------| | DomainNumber | Res…

ZZULIOJ 2521: 文本修正

2521: 文本修正 题目描述 Chika接到了去检查河南省算法竞赛题面的任务&#xff0c;她发现所有单词"Henan"的首字母都没有大写。她需要去修正文本中的所有错误。换句话说&#xff0c;她需要把所有单词"henan"的首字母从"h"替换为"H"&…