LarryDpk
发布于 2025-05-11 / 7 阅读
0

PriorityQueue简介

PriorityQueue简介

一、总体概述

java.util.PriorityQueue<E> 基于最小堆 实现的可伸缩队列,符合队列 FIFO 的“取元素”语义,但优先级由比较器决定。它实现 Queue<E>Serializable默认小顶堆(最小值最高优先),可通过自定义 Comparator 变为最大堆或任意优先策略。


二、底层实现原理

维度 说明
核心结构 二叉堆:用 Object[] queue 存储,根节点索引 0,i 的左右孩子为 2i+1 / 2i+2
比较逻辑 - 若构造时传入 Comparator<? superE>,永远用它比较
- 否则要求 E 实现 Comparable<E>,按自然顺序
动态扩容 初始容量默认 11;插入时若满 → Arrays.copyOf 扩成 old + (old < 64 ? old + 2 : old >> 1)
堆调节 - 上浮siftUp() —— 新元素插入末尾,沿父方向与更小者交换
- 下沉siftDown() —— 弹出堆顶后把最后一个元素放根,自顶向下与更小孩子交换
稳定性 不保证稳定;相等优先级元素的顺序可能变化

三、复杂度分析

操作 时间复杂度 备注
offer/add O(logn) 上浮
poll/remove() O(logn) 下沉
peek O(1) 直接读根
contains / remove(Object) O(n) 线性扫描
迭代 O(n) 非堆序(不可排序遍历)
空间 O(n) 数组持有全部元素

常数因子:比 TreeSet/TreeMap 更低(无额外指针、旋转),因此在仅需堆顶最值时更快。


四、完整 API 速查

分类 常用方法 说明
构造 PriorityQueue() / PriorityQueue(int cap) 默认小顶堆
PriorityQueue(Comparator) 自定义优先级
PriorityQueue(Collection<? extends E>) 线性堆化 O(n)
插入 boolean add(E)(抛异常)
boolean offer(E)(返回 false)
放入元素
访问 E peek() 查看堆顶,不删除
弹出 E poll() / E remove() 删除并返回堆顶
remove(Object) 删除任意元素
批量 addAll(Collection) / clear()
属性 size() / isEmpty()
遍历 Iterator<E> 任意顺序,Fail‑fast
数组化 toArray() / toArray(T[])

五、注意事项 & 易错点

⚠️ 点 细节
null 禁止 插入 nullNullPointerException(优先级无法比较)
迭代顺序 iterator() 不是 优先级顺;仅保证包含全部元素
相等元素 允许重复;比较器返回 0 则视为同优先级,不会去重
修改元素字段 若元素的 compareTo/compare 结果在队列中被修改 ⇒ 堆顺序被破坏,应 移除‑修改‑重插
contains/remove(Object) 内部线性查找,O(n),大量调用需谨慎
容量扩容 自动扩容但可能多次 System.arraycopy,若已知规模可预设容量
线程安全 非线程安全:多线程并发写需外部同步;在并发场景用 PriorityBlockingQueue(无界)或自己加锁

六、典型使用场景

场景 说明
最短路径 Dijkstra / A* int[] {node, dist} 或自定义 Node,最小堆按距离
K 个有序数组归并 堆存每个数组当前头元素,弹出后推新元素
Top‑K 维护大小 K 的“最大堆”或“最小堆”
流式中位数 双堆:大顶堆 + 小顶堆
任务调度 根据时间戳 / 权重弹出下一个任务
多路归并排序 外排、磁盘归并

七、线程安全方案

方案 说明
Collections.synchronizedCollection(pq) 简单互斥包装,粒度大
PriorityBlockingQueue JDK并发包,基于 无锁堆 + ReentrantLock
适用生产消费者
手动锁 ReentrantLock / synchronized 包围关键操作

八、性能细节

  1. 批量构造更快new PriorityQueue<>(collection) 采用 Floyd 堆化,O(n);逐个 addO(nlogn)
  2. 最大堆技巧PriorityQueue<Integer> max = new PriorityQueue<>((a,b)->b-a);
  3. 数组缓存:JDK17 开始 siftUp / siftDown 内联优化,实测百万级插入 ≈7–10M ops/s(取决于对象大小)。
  4. 小顶堆变 K 小:当只关心 Top‑K 最大 元素时,用 固定容量最小堆,当大小>k 时 poll()。

九、示例代码

import java.util.*;  
  
public class PriorityQueueDemo {  
  
 // Top K 最大元素  
 static List<Integer> topK(int[] nums, int k) { PriorityQueue<Integer> minHeap = new PriorityQueue<>(k); // 小顶堆  
 for (int n : nums) { if (minHeap.size() < k) minHeap.offer(n); else if (n > minHeap.peek()) { minHeap.poll(); minHeap.offer(n); } } return new ArrayList<>(minHeap); // 未排序  
 }  
 // Dijkstra static int dijkstra(int n, List<int[]>[] g, int src, int dst) { int[] dist = new int[n]; Arrays.fill(dist, Integer.MAX_VALUE); dist[src] = 0; PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1])); pq.offer(new int[]{src, 0}); while (!pq.isEmpty()) { int[] cur = pq.poll(); int u = cur[0], d = cur[1]; if (u == dst) return d; if (d > dist[u]) continue; for (int[] e : g[u]) { int v = e[0], w = e[1]; if (dist[u] + w < dist[v]) { dist[v] = dist[u] + w; pq.offer(new int[]{v, dist[v]}); } } } return -1; }  
 public static void main(String[] args) { // Top‑K System.out.println(topK(new int[]{5, 2, 9, 1, 7, 6}, 3)); // [6,7,9]  
 // 最大堆示例  
 PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a,b)->b-a); maxHeap.addAll(Arrays.asList(4, 1, 7)); System.out.println(maxHeap.poll()); // 7 }}  

🔚 小结

  • PriorityQueue = 最小(或自定义)堆 + 动态数组
  • 提供 O(logn) 的入队/出队,O(1) 查看堆顶;
  • 适用于 Top‑K、Dijkstra、K 路归并、流式统计 等;
  • 非线程安全,并发场景选 PriorityBlockingQueue 或外部加锁;
  • 避免迭代器顺序误解、避免修改元素优先级后不重平衡、合理设置初始容量。