在刷leecode的23题MergekSortedLists时,看到一种解法是使用PriorityQueue来解决的,官方答案中只有python的代码,尝试了之后发现java中也有PriorityQueue类,因此记录一下对这个类的分析。
PriorityQueue类位于java.util包,方法也不多,是一个基于优先级堆的无界优先级队列,队列数据的排序依据于元素自身的比较器或者是在PriorityQueue构造时传入一个比较器。说到底就是一个有序的队列。
PriorityQueue继承自抽象队列AbstractQueue并实现了Serializable接口。
首先看一下PriorityQueue类的几个构造函数
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) {
// 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;
}
创建一个大小为initialCapacity的Object数组,如果未指明initialCapacity,默认大小为11。如果有comparator则将comparator对象赋给PriorityQueue对象的comparator属性。
还有一个构造函数是以一个现有的集合类来构造一个PriorityQueue对象。
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;
initFromCollection(c);
}
}
如果原集合是一个有序集合,那么将其自带的comparator对象赋给PriorityQueue对象的comparator属性,然后调用initElementsFromCollection方法
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)
a = Arrays.copyOf(a, a.length, Object[].class);
int len = a.length;
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;
}
将集合转换为一个Object数组,赋值给PriorityQueue对象的queue属性,并初始化PriorityQueue对象的大小。
如果原集合是一个PriorityQueue对象,那么将其自带的comparator对象赋给新的PriorityQueue对象的comparator属性,然后调用initFromPriorityQueue方法
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
if (c.getClass() == PriorityQueue.class) {
this.queue = c.toArray();
this.size = c.size();
} else {
initFromCollection(c);
}
}
如果构造函数的参数是一个PriorityQueue对象,那么将PriorityQueue对象转化为一个Object数组赋值给PriorityQueue对象的queue属性,并初始化PriorityQueue对象的大小;如果参数是PriorityQueue的子类对象,那么会调用initFromCollection函数进行初始化。
此处要注意的是instanceof判断中 子类 instanceof 父类 = true,子类.getClass() == 父类.class = false。
因此可见除了PriorityQueue类和实现了SortedSet接口的类将都由initFromCollection函数进行初始化。
private void initFromCollection(Collection<? extends E> c) {
initElementsFromCollection(c);
heapify();
}
首先调用函数initElementsFromCollection
初始化PriorityQueue的queue和size属性,然后调用heapify函数将queue堆排序,完成PriorityQueue对象的构造。
接下来我们看一下PriorityQueue的几个操作函数。
由于add
和offer
两个函数是一种实现,所以在一起说
/**
* Inserts the specified element into this priority queue.
*
* @return {@code true} (as specified by {@link Collection#add})
* @throws ClassCastException if the specified element cannot be
* compared with elements currently in this priority queue
* according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean add(E e) {
return offer(e);
}
/**
* Inserts the specified element into this priority queue.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws ClassCastException if the specified element cannot be
* compared with elements currently in this priority queue
* according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
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
siftUp(i, e);
return true;
}
可以看到插入新元素时,会先将修改计数modCount加一,然后检查queue数组的大小是否够用,如果不够将会调用grow
函数扩大空间。
/**
* Increases the capacity of the array.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
当queue数组原来的大小小于64时,数组大小将会翻倍,否则会增大50%,如果超过最大值会抛出OutOfMemoryError。当原数组queue非空,那么将调用siftUp
函数将新插入的值存放到合适的位置,使得queue数组保持最小堆状态。
接下来是remove函数,这个函数会将第一个符合的对象移除。
/**
* Removes a single instance of the specified element from this queue,
* if it is present. More formally, removes an element {@code e} such
* that {@code o.equals(e)}, if this queue contains one or more such
* elements. Returns {@code true} if and only if this queue contained
* the specified element (or equivalently, if this queue changed as a
* result of the call).
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
*/
public boolean remove(Object o) {
int i = indexOf(o);
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
/**
* Version of remove using reference equality, not equals.
* Needed by iterator.remove.
*
* @param o element to be removed from this queue, if present
* @return {@code true} if removed
*/
boolean removeEq(Object o) {
for (int i = 0; i < size; i++) {
if (o == queue[i]) {
removeAt(i);
return true;
}
}
return false;
}
remove
函数和removeEq
函数的区别在于使用==
还是.equals()
来判断相同。具体的移除逻辑在removeAt
函数中
/**
* Removes the ith element from queue.
*
* Normally this method leaves the elements at up to i-1,
* inclusive, untouched. Under these circumstances, it returns
* null. Occasionally, in order to maintain the heap invariant,
* it must swap a later element of the list with one earlier than
* i. Under these circumstances, this method returns the element
* that was previously at the end of the list and is now at some
* position before i. This fact is used by iterator.remove so as to
* avoid missing traversing elements.
*/
@SuppressWarnings("unchecked")
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];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
函数首先将修改计数modCount增加1,而后判断i对应的是否是queue数组的最后一个元素,如果是,那么直接将其置为null,否则将i对应的元素挖掉并将queue数组的最后一个元素补入,并执行siftDown
让queue数组保持最小堆状态。
peek
函数将会返回数组queue中的首个元素,而poll
函数会返回数组queue中的首个元素并将数组首个元素挖掉并将最后一个元素补入执行siftDown
让queue数组保持最小堆状态。实现上类似执行了removeAt(0)。
综上PriorityQueue比较适合用于处理有排序需求的队列问题。
附上leecode链接