一、摘要
在前几篇文章中,咱们了解到,Queue 的实现类有 ArrayDeque、LinkedList、PriorityQueue。
在上一章节中,陆续的介绍到 ArrayDeque 和 LinkedList 的数据结构和算法实现,今天咱们来介绍一下PriorityQueue 这个类,一个特殊的优先级队列。如果有理解不当之处,欢迎指正。
二、简介
PriorityQueue 并没有直接实现 Queue接口,而是通过继承 AbstractQueue 类来实现 Queue 接口一些方法,在 Java 定义中,PriorityQueue 是一个基于优先级的无界优先队列。
通俗的说,添加到 PriorityQueue 队列里面的元素都经过了排序处理,默认按照自然顺序,也可以通过 Comparator 接口进行自定义排序。
优先队列的作用是保证每次取出的元素都是队列中权值最小的。
如果猿友们了解过 TreeMap 的实现,会发现 PriorityQueue 排序实现与之类似。
PriorityQueue 是采用树形结构来描述元素的存储,具体说是通过完全二叉树实现一个小顶堆,在物理存储方面,PriorityQueue 底层通过数组来实现元素的存储。
在上图中,我们给每个元素的下标做了标注,足够细心的你会发现,数组下标,存在以下关系:
leftNo = parentNo * 2 + 1
rightNo = parentNo * 2 + 2
parentNo = (currentNo -1) / 2
各个参数具体含义如下:
- parentNo:表示父节点下标;
- leftNo:表示子元素左节点下标;
- rightNo:表示子元素右节点下标;
- currentNo:表示当前元素节点下标;
通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储元素实现二叉树结构的原因。
2.1、源码介绍
PriorityQueue 源码定义如下:
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
/**默认容量为11*/
private static final int DEFAULT_INITIAL_CAPACITY = 11;
/**队列容器*/
transient Object[] queue;
/**队列长度*/
private int size = 0;
/**比较器,为null使用自然排序*/
private final Comparator<? super E> comparator;
......
}
从定义中可以得出,PriorityQueue 有3个比较核心的变量属性,内容如下:
- queue:表示存放元素的数组
- comparator:表示比较器对象,如果为空,使用自然排序
- size:表示队列长度
我们再来看看 PriorityQueue 类的构造方法,PriorityQueue 构造方法分两类,一种是默认初始化、另一种是传入 Comparator 接口比较器,内容如下:
默认初始化,使用自然排序方式进行插入,源码如下:
public PriorityQueue() {
//默认数组长度为11,传入比较器为null
this(DEFAULT_INITIAL_CAPACITY, null);
}
调用的方法,源码如下:
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
//初始化容量小于 1,抛异常
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
自定义比较器初始化,使用 comparator 接口比较器作为参数传入,源码如下:
public PriorityQueue(Comparator<? super E> comparator) {
//传入比较器 comparator
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
这两者初始化方式,咱们在下文会一一讲到。
在介绍 PriorityQueue 实现的方法之前,咱们了解到,Queue 接口定义有如下方法:
同样的 PriorityQueue 也实现了这些方法,PriorityQueue 方法虽然定义的很多,但无非就是对容器进行添加、删除、查询操作,下面我们分别来看看各个操作方法的实现过程。
三、常见方法介绍
3.1、添加方法
PriorityQueue 的添加方法有 2 种,分别是add(E e)
和offer(E e)
,两者语义相同,都是向优先队列中插入元素,只是Queue
接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则返回false
。
3.1.1、offer 方法
offer 方法图解实现流程如下:
新加入的元素可能会破坏小顶堆的性质,在 c、d 两步会进行调整。
offer 方法的实现,源码如下:
public boolean offer(E e) {
//不允许放入null元素
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
//自动扩容
grow(i + 1);
size = i + 1;
//队列原来为空,这是插入的第一个元素
if (i == 0)
queue[0