Priority Queue
JDK5开始引入, 能够根据对象的优先级来进行排序,要求对象实现Comparable或Comparator接口中的比较方法,底层采用堆来实现。
根据优先级对数据进行排列的数据结构
基本介绍:
- 根据优先级来排列的队列,存储的对象一定要实现Comparable接口,否则将抛出异常
- 底层实际上也是通过数组来进行存储数据的, 空间不限
- 实现了Collection和Iterator接口,通过Iterator遍历数据时并不保证有序的,如果要有序输出,可以通过Arrays.sort(pq.toArray())
- 非同步,多个线程同时操作会发生并发异常,可以使用JUC中线程安全的PriorityBlockingQueue类
- 常用方法:
- 时间复杂度 O(logN)的方法:offer、poll、remove、add,
- 线性时间方法:remove(object), contains(object)
- 常数时间方法:peek、element、size
- 队列中的头元素优先级最小
- 使用堆来保证元素的有序性
源码实现:
-
采用数组,默认大小 DEFAULT_INITIAL_CAPACITY = 11
-
使用堆来保证数据的有序性,queue[0] 优先级最低,queue[n] 的两个孩子queue[2n+1] and queue[2(n+1)]
-
假设Comparable接口中实现的方法类似下面:
@Override public int compareTo(int o) { return this.x - o.x; }
这样我们的优先级队列中就采用小顶堆
的数据结构来实现, 否则采用大顶堆
下面采用小顶堆的形式进行分析源码
对象初始化:
// 无参构造: 默认大小11
public PriorityQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityQueue(int initialCapacity) {
this(initialCapacity, null);
}
// 指定比较器
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
/**
有参构造方法:指定初始化大小,指定比较器
*/
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
// 传入一个集合来创建PriorityQueue, 如果c中实现了Comparator接口,则使用c中的Comparator
public PriorityQueue(Collection<? extends E> c) {
if (c instanceof SortedSet<?>) {
SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
this.comparator = (Comparator<? super E>) ss.comparator();
initElementsFromCollection(ss);
}
else if (c instanceof PriorityQueue<?>) {
PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
this.comparator = (Comparator<? super E>) pq.comparator();
initFromPriorityQueue(pq);
}
else {
this.comparator = null;
// 通过c来初始化PriorityQueue
initFromCollection(c);
}
}
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
// 如果c是由PriorityQueue实例化的,那么将c中的数据赋值给当前正在实例化的PriorityQueue
if (c.getClass() == PriorityQueue.class) {
this.queue = c.toArray();
this.size = c.size();
} else {
initFromCollection(c);
}
}
// 将一个Collection类型的数据初始化到当前正在创建的对象中
private void initElementsFromCollection(Collection<? extends E> c) {
Object[] a = c.toArray();
// If c.toArray incorrectly doesn't return Object[], copy it.
if (a.getClass() != Object[].class)
// copy a数组并转为 Object[]类型
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
// 如果a数组中含有null,那么抛出异常
if (len == 1 || this.comparator != null)
for (int i = 0; i < len; i++)
if (a[i] == null)
throw new NullPointerException();
this.queue = a;
this.size = a.length;
}
// 这个方法在调用initFromPriorityQueue()方法中执行,并构造一个标准的堆
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
插入元素:
动画演示:
新增元素:
将元素插入最后一个位置后,此时的堆并没有满足父节点小于孩子节点规则,因此需要对堆进行相应的调整。
// add 方法最终也是调用的offer
public boolean offer(E e) {
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] = e;
else
// 在位置i插入e
siftUp(i, e);
return true;
}
// 在k位置插入元素x后,进行上浮,判断是否满足小顶堆规则
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// PriorityQueue对象没有实现比较器的插入操作,使用待插入元素的比较器进行大小比较
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
// 得到当前元素的父节点的位置,相当于(k - 1) / 2
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 如果待插入的元素大于父节点,则说明不会破坏小顶堆的特性,直接将元素放在k位置即可
if (key.compareTo((E) e) >= 0)
break;
// 待插入的元素小于父节点,将父节点放到k位置,循环向上找到x的合适位置
queue[k] = e;
k = parent;
}
queue[k] = key;
}
// PriorityQueue对象有比较器的插入操作, 类似上面的方法
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)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
移除元素:
这里展示移除元素4:
// 移除堆顶的元素,同时调用siftDown来调整堆的结构
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;
}
public boolean remove(Object o) {
// 遍历数组找到需要remove的元素的位置
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
private 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];
// 将最后一个位置置为null
queue[s] = null;
// 具体删除元素的方法,根据i位置的元素与moved比较,是否对moved下调(moved是放i位置,还是i的孩子位置)
siftDown(i, moved);
// 成立说明,moved刚好放到了i的位置
if (queue[i] == moved) {
// 判断是否对i位置的元素进行上浮操作
siftUp(i, moved);
// i位置的元素已经上浮
if (queue[i] != moved)
return moved;
}
}
return null;
}
// 对元素进行下浮操作, 即k位置的元素被删除后,由x来代替i位置,对x进行下浮操作
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
// k: 待删除的元素的位置, x: 数组最后一个位置的元素,最后要将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
// k 不能超过最后一个叶子节点的位置,如果超过,直接在相应位置填入key
while (k < half) {
// 得到待删除元素位置的左孩子位置
int child = (k << 1) + 1; // assume left child is least
// 得到左孩子的元素
Object c = queue[child];
// 右孩子位置
int right = child + 1;
// 如果右孩子存在 左孩子是否大于右孩子,目的是将最小的孩子赋值给c
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
// 判断元素key是否小于c(c为待插入元素最小的孩子节点),满足则break,直接将key放到待删除元素位置,这样肯定是满足堆的特性
if (key.compareTo((E) c) <= 0)
break;
// 没有找到合适的位置继续向上查找,直到找到
queue[k] = c;
k = child;
}
queue[k] = key;
}
// 跟上面方法类似
@SuppressWarnings("unchecked")
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;
}
扩容操作:
// 在插入元素时,offer方法调用: minCapacity = oldCapacity + 1
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
// 如果oldCapacity < 64 那么扩容原来的二倍 + 2,
// 否则扩容为原来的1.5倍
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
// 如果计算出的扩容长度超过了默认的最大值,那么调用hugeCapacity来判断是扩容到Integer.MAX_VALUE,还是MAX_ARRAY_SIZE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将原来的数据copy到长度为newCapacity的新数组中
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}