什么是 Queue
数据结构中的队列,先进先出式的数据结构,所有新元素都插入队列的末尾,移除元素都移除队列的头部。主要注意的时,Java中的Queue是一个接口。
Queue 源码分析
public interface Queue<E> extends Collection<E> {
boolean add(E e); //往队列插入元素,如果出现异常会抛出异常
boolean offer(E e); //往队列插入元素,如果出现异常则返回false
E remove(); //移除队列元素,如果出现异常会抛出异常
E poll(); //移除队列元素,如果出现异常则返回null
E element(); //获取队列头部元素,如果出现异常会抛出异常
E peek(); //获取队列头部元素,如果出现异常则返回null
}
上面六个函数总体上分为两类:安全的会进行容量检查的(add,remove,element),如果队列没有值,则取元素会抛出IlleaglStatementException异常。不安全的不进行容量控制的(offer,poll,peek )。
AbstractQueue 是Queue 的抽象实现类, AbstractQueue 也继承自 AbstractCollection 。AbstractQueue 实现的方法不多,主要就 add、remove、element 三个方法的操作失败抛出了异常。
什么是 PriorityQueue
PriorityQueue 优先级队列, ,不同于普通的遵循FIFO(先进先出)规则的队列,每次都选出优先级最高的元素出队,优先队列里实际是维护了这样的一个堆,通过堆使得每次取出的元素总是最小的(用户可以自定义比较方法,相当于用户设定优先级)。 PriorityQueue 是一个小顶堆,是非线程安全的;PriorityQueue不是有序的,只有堆顶存储着最小的元素;从数据的存储结构看, PriorityQueue 一个数组;从逻辑结构看, PriorityQueue 是一棵平衡二叉树。
什么是 PriorityQueue
PriorityQueue 优先级队列, ,不同于普通的遵循FIFO(先进先出)规则的队列,每次都选出优先级最高的元素出队,优先队列里实际是维护了这样的一个堆,通过堆使得每次取出的元素总是最小的(用户可以自定义比较方法,相当于用户设定优先级)。 PriorityQueue 是一个小顶堆,是非线程安全的;PriorityQueue不是有序的,只有堆顶存储着最小的元素;从数据的存储结构看, PriorityQueue 一个数组;从逻辑结构看, PriorityQueue 是一棵平衡二叉树。
PriorityQueue 源码分析
继承关系
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable{
// 略
}
PriorityQueue 继承了AbstractQueue 类,而AbstractQueue 类实现了Queue接口。
主要属性
// 默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 存储元素的数组,Object类型的
transient Object[] queue;
// 元素个数
int size;
// 比较器
private final Comparator<? super E> comparator;
// 修改次数
transient int modCount;
transient Object[] queue
源码注释
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.
优先级队列是一个平衡的二叉树堆,queue[n]的两个子节点queue[2n+1] and queue[2(n+1)]。 优先级队列 按照 comparator 进行排序或者 按自然顺序排序。 如果 comparator 为null ,堆中每一个节点 n 以及 n的后代d, n<=d。假设队列为非空,则具有最小值的元素位于queue [0]中。
常用构造函数
- 1、创建一个优先队列对象,默认大小,队列中的元素按照自然顺序排序。
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
- 2、创建一个指定大小的优先队列对象,队列中的元素按照自然顺序排序。
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
- 3、创建一个默认大小(11)的优先队列对象,队列中的元素按照指定比较器进行排序。
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;
}
add(e)/offer(e)方法介绍
add方法
插入一个元素到优先队列中
add方法的源代码如下:
public boolean add(E e) {
return offer(e);
}
从源码中可以看出add方法直接调用了offer方法。
offer(e)方法
public boolean offer(E e) {
if (e == null) //检查是否为null, 如果为null 则抛空指针异常
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length) // 检查容量是否足够, 不足进行扩容
grow(i + 1); // 扩容函数
siftUp(i, e); // 在数组的末尾插入新的元素,向上调整,使之保持为二叉堆的特性。
size = i + 1;
return true;
}
1)首先检查要添加的元素e是否为null,如果为null,则抛空指针异常,如果不为null,则进行2
2)判断数组是否已满,如果已满,则进行扩容,否则将元素加入到数组末尾即可。
由于这个数组表示的是一个“二叉堆”,因此还需要进行相应的调整操作,使得这个数组在添加元素之后依然保持的是二叉堆的特性。
grow(int minCapacity)方法
//增加数组的容量
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
//如果比较小,则扩容为原来的2倍,否则只扩容为原来的1.5倍
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);
}
hugeCapacity(int minCapacity) 方法
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
注:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public static final int MAX_VALUE = 0x7fffffff;
当 大于 MAX_ARRAY_SIZE 时扩容记性了特殊处理
siftUp(int k, E x)方法
将元素x插入到queue[k]位置上,并进行调整使之具有二叉堆特性
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x); // 使用比较器 comparator
else
siftUpComparable(k, x);
}
siftUpUsingComparator和siftUpComparable源码如下
从 位置 k 向数组起始位置 调整使之具有二叉堆特性,自下而上的堆化,
也就是 二叉堆插入元素思想操作:
可参见 拜托,面试别再问我堆(排序)了!
siftUpComparable(int k, E x)方法
@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; // 找 parent 父节点
Object e = queue[parent];
if (key.compareTo((E) e) >= 0) // 比较 按 自然顺序 排列
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
siftUpUsingComparator(int k, E x)方法
@SuppressWarnings("unchecked")
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0) // 使用比较器 comparator
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
poll()方法介绍
取出优先队列中的第一个元素
public E poll() {
if (size == 0) // 优先队列大小判断
return null;
int s = --size;
modCount++;
E result = (E) queue[0]; // 取出第一个元素
E x = (E) queue[s]; // 取出最后一个元素
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}
poll方法:取出queue[0]元素,然后将queue[size-1]插入到queue[0],然后自上而下堆化来保持二叉堆的特性。
siftDown(int k, E x)方法
//将元素x存储在queue[k],并进行相应的调整
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
siftDownComparable(int k, E x)方法
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) //两个儿子节点较小的那一个
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
siftDownUsingComparator(int k, E x)方法
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 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;
}
remove(Object o) 方法介绍
移除指定元素
public boolean remove(Object o) {
int i = indexOf(o); // 判断位置
if (i == -1) //没有在数组中找到
return false;
else {
removeAt(i); //进行删除并调整
return true;
}
}
indexOf(Object o)方法
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < size; i++) // 数组遍历过程
if (o.equals(queue[i]))
return i;
}
return -1;
}
找到对象o在数组中出现的第一个索引
emoveAt(int i)方法
E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s]; // 取出最后一个元素
queue[s] = null;
siftDown(i, moved); //从i向下调整堆
if (queue[i] == moved) { // 如果发现调整后 moved 还在 i 位置没有下沉,向上调整看是否上浮
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
堆插入元素与删除堆顶元素操作
以下面这个对为例
用数组来存储为:
插入元素
往堆中插入一个元素后,需要继续满足堆的两个特性,即:
(1)堆是一颗完全二叉树;
(2)堆中某个节点的值总是不大于(或不小于)其父节点的值。
把元素插入到最后一层最后一个节点往后一位的位置,也就是在数组的末尾插入新的元素
,插入之后可能不再满足条件(2),这时候我们需要调整,自下而上的堆化。
上面那个堆插入元素2,我们把它放在9后面,这时不满足条件(2)了,我们就需要堆化。(这是一个小顶堆)
从插入元素的过程,我们知道每次与
的位置进行比较,所以,插入元素的时间复杂度为
。
删除堆顶元素
删除了堆顶元素后,要使得还满足堆的两个特性。把最后一个元素移到根节点的位置,这时候就满足条件(1),之后就需要堆化了使它满足条件(2)
从删除元素的过程,我们知道把最后一个元素拿到根节点后,每次与
和
位置的元素比较,取其小者,所以,删除元素的时间复杂度也为
。
回到 remove(Object o) 方法 ,不是删除堆顶元而是删除指定元素,源码中会先找Object o在数组中位置 i,再取出最后一个元素从i自上而下堆化,如果最后一个元素没有下沉,会从i自下而上堆化
参考:
拜托,面试别再问我堆(排序)了!
https://mp.weixin.qq.com/s/AF2tMHfofG8b51yIyaIReg