前言:
前文介绍了Stack这种数据结构类型,它符合后进先出(LIFO)的操作顺序。
今天介绍与其相反操作顺序的一种数据结构,Queue(队列),它符合先进先出(FIFO)的操作顺序
从网络上截一个图(如有侵权,请联系作者),简单表示下队列结构
a1是最先进入队列的元素,现在排在队头,an是最后入队的元素,排在队尾
执行出队操作的时候,最先进入队列的a1元素,最先出队
1.Queue
java.util.Queue是一个接口,实现类有以下
/**
* @see java.util.Collection
* @see LinkedList
* @see PriorityQueue
* @see java.util.concurrent.LinkedBlockingQueue
* @see java.util.concurrent.BlockingQueue
* @see java.util.concurrent.ArrayBlockingQueue
* @see java.util.concurrent.LinkedBlockingQueue
* @see java.util.concurrent.PriorityBlockingQueue
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public interface Queue<E> extends Collection<E> {
之前在介绍LinkedList的时候,也能看到其实现了Deque接口,Deque继承了Queue,所以LinkedList实现了Queue接口。
今天介绍下另一种Queue的实现类,PriorityQueue
2.PriorityQueue数据结构分析
关于PriorityQueue的数据结构,就是堆。
关于堆的内容笔者不再详述,可以参考:小灰漫画 什么是二叉堆
笔者之前也有写过类似的博客:https://blog.csdn.net/qq_26323323/article/details/79708103
借用小灰漫画的图就是:
二叉堆本质上是一个完全二叉树,它分为两个类型:最大堆和最小堆
最大堆就是:每一个父节点的值,都大于等于它左右子节点的值,如下所示
最小堆就是:每一个父节点的值,都小于等于它左右子节点的值,如下所示:
依据这种堆结构,在堆顶的元素要么是最大的元素,要么是最小的元素,可以快速查找最大最小值
3.PriorityQueue结构分析
/**
The elements of the priority queue are ordered according to their
* {@linkplain Comparable natural ordering}, or by a {@link Comparator}
*/
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
// 默认容量是11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 使用数组来存储元素
transient Object[] queue;
// 与ArrayList类似,也有一个size属性
private int size = 0;
// 如何实现优先级的队列呢,主要就是靠这个比较器
private final Comparator<? super E> comparator;
总结:可以看出,其与ArrayList的成员变量差不多,主要区别就是一个比较器comparator
4.构造方法
// 1.空参构造,默认容量是11
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
// 2.给定初始容量
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
// 3.给定比较器
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
// 4.同时给定初始容量和比较器
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
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
构造方法主要是围绕初始容量和比较器来创建的
5.添加操作
// add()
public boolean add(E e) {
return offer(e);
}
// offer()
public boolean offer(E e) {}
可以看到,add方法直接调用了offer方法,那么我们直接看下offer方法的实现即可
// PriorityQueue.offer()
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
// 与ArrayList类似,在添加之前需要先看是否需要扩容
// size是queue里真正有数据的大小
// 而queue.length初始是11,是大于size的
// 当size>=queue.length时,需要进行扩容操作
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
// 主要看这里,是如何实现优先级的存放的
siftUp(i, e);
return true;
}
// 扩容操作
// 当前队列长度小于64,则扩容两倍;否则扩容1.5倍
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
// PriorityQueue.siftUp()
private void siftUp(int k, E x) {
// 如果指定了比较器,则使用指定的,否则使用默认比较器
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// PriorityQueue.siftUpUsingComparator()
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
// 1.获取父节点,也就是(k-1)/2位置处的值
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 2.如果当前节点大于父节点,则将k位置赋值为x
// 否则将k位置parent位置处的值e
// 总体来说实现的是一个最小堆,父节点值小于其左右子节点
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
// PriorityQueue.siftUpComparable()
// 与siftUpUsingComparator方法类似,只是用了对象本身的比较器而已
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
总结:添加方法主要就是为了实现一个最小堆,将最小的元素往上移动
6.删除操作
// PriorityQueue.poll()
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
// 1.获取堆顶元素
E result = (E) queue[0];
// 2.获取最后一个元素,在整个堆中比较大
E x = (E) queue[s];
queue[s] = null;
// s=0说明,当前元素已经是最后一个元素了,
// s!=0时,需要将x置为堆顶,然后做下沉操作
if (s != 0)
siftDown(0, x);
return result;
}
// PriorityQueue.siftDown()
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
// PriorityQueue.siftDownUsingComparator()
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
// k=1,为堆顶元素,执行下沉操作
// 比较其左子节点和右子节点,
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
// 一直比较到当前节点小于某一节点为止
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
被删除的节点就是堆顶元素,也就是当前最小的元素,删除之后需要补充一个元素,一般来说就是补充最后一个元素到堆顶,这个元素肯定是比较大的元素,不符合最小堆的规范,则需要将这个元素逐渐下沉,下沉到合适的位置即可
7.查询操作
public E peek() {
return (size == 0) ? null : (E) queue[0];
}
查询只有一个获取堆顶元素的方法,获取的就是整个堆中最大或最小的值