PriorityQueue
定位说明
Queue 系列第 2 篇详篇。最小堆落地:offer/poll 的上浮下沉、数组扩容、Comparator 定制排序。刷题高频(Top K、合并 K 链表、Dijkstra)。
一、概念与基本特性
PriorityQueue<E> 是一个基于堆(最小堆)实现的优先级队列,元素按照 优先级升序排列,每次出队返回最小元素(peek/poll
返回堆顶元素)。
- 底层结构:动态数组
Object[] queue作为二叉堆容器; - 默认构建的是最小堆;
- 插入复杂度 O(log n),删除堆顶复杂度 O(log n),访问堆顶 O(1);
- 支持自定义比较器(
Comparator)或元素自身实现Comparable接口; - 非线程安全;
- 不允许插入 null。
二、源码结构与数据字段
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {
transient Object[] queue; // 堆容器,数组实现
private int size = 0; // 实际元素数量
private final Comparator<? super E> comparator; // 自定义比较器
...
}
queue[]:底层使用数组保存堆结构;size:记录当前元素个数;comparator:可选比较器;若为空,则元素必须实现Comparable。
三、添加元素:offer() / add()
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
int i = size;
if (i >= queue.length) grow(i + 1); // 扩容
siftUp(i, e); // 向上调整,维护堆
size = i + 1;
return true;
}
siftUp 操作(向上堆化)
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
对于最小堆,插入新元素后从当前位置向上比较并交换,直到满足“父节点 ≤ 当前节点”规则。
示例结构变更(最小堆):
插入前: [2, 5, 3]
插入 1: [1, 2, 3, 5] // 1 向上堆化,成为新堆顶
四、删除元素:poll()
public E poll() {
if (size == 0)
return null;
int s = --size;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x); // 末尾元素替换堆顶后下沉
return result;
}
siftDown 操作(向下堆化)
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
删除堆顶元素后,将末尾元素放到堆顶并进行向下调整,保证堆有序性。
五、扩容机制
默认初始容量为 11,扩容策略参考 ArrayList,约为原容量的 1.5 倍:
private void grow(int minCapacity) {
int oldCapacity = queue.length;
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) : // 小数组加倍
(oldCapacity >> 1)); // 大数组按 1.5 倍扩容
queue = Arrays.copyOf(queue, newCapacity);
}
- 扩容后旧数组内容通过
Arrays.copyOf拷贝,成本为 O(n)。
六、最小堆如何维护?
核心在于每次插入或删除后通过 siftUp / siftDown 调整元素顺序,确保满足:
queue[i] <= queue[2i+1] && queue[i] <= queue[2i+2](父节点小于等于左右子节点)
最小堆可以视为完全二叉树,每个节点的下标与其子节点的下标满足:
- 左子节点下标:
2 * i + 1 - 右子节点下标:
2 * i + 2 - 父节点下标:
(i - 1) / 2
七、比较器支持
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 默认升序
PriorityQueue<Integer> pq2 = new PriorityQueue<>((a, b) -> b - a); // 自定义降序
如果自定义了 Comparator,所有堆调整操作都会使用传入的规则,而不是元素本身的 compareTo()。
八、遍历与非排序
虽然 PriorityQueue 实现了 Iterable 接口,可以用增强 for 循环遍历:
for (Integer i : pq) {
System.out.println(i);
}
但 遍历顺序不是堆序或有序,只是按数组顺序遍历底层数组。若需要有序输出,应反复 poll()。
⬅️ ArrayDeque 🏠 00-Java ➡️ 00-Map总览
💬 评论