PriorityQueue
是 JDK 1.5 引入的一个重要集合类,它实现了一个基于优先级的队列。与普通的 Queue
不同,PriorityQueue
中元素的出队顺序由元素的优先级决定,即优先级最高的元素会最先出队。本文将详细探讨 PriorityQueue
的实现细节、常见应用场景以及与其他集合类的对比。
1. PriorityQueue
的实现原理
PriorityQueue
是基于二叉堆(Binary Heap)实现的,而二叉堆是一种完全二叉树。PriorityQueue
通过堆元素的上浮和下沉操作,确保插入和删除操作的时间复杂度为 (O(\log n))。
1.1 底层数据结构
PriorityQueue
底层使用一个可变长的数组来存储数据。初始容量为 11,当需要更大的容量时,会按需扩容。
java
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue; // 存储元素的数组
private int size = 0; // 队列中元素的数量
1.2 插入元素
插入元素时,PriorityQueue
会先将元素添加到数组的末尾,然后通过上浮操作(sift up)将其移动到合适的位置,以保持堆的性质。
java
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
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;
}
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
1.3 删除堆顶元素
删除堆顶元素时,PriorityQueue
会将堆顶元素与数组的最后一个元素交换,然后通过下沉操作(sift down)将交换后的元素移动到合适的位置,以保持堆的性质。
java
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;
}
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super 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 && ((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;
}
2. 常见应用场景
PriorityQueue
在许多算法和应用中都有广泛的应用,以下是几个经典的场景:
2.1 堆排序
堆排序是一种基于堆数据结构的排序算法,其时间复杂度为 (O(n \log n))。PriorityQueue
可以方便地用于实现堆排序:
java
public static <E> List<E> heapSort(Collection<E> collection) {
PriorityQueue<E> heap = new PriorityQueue<>(collection);
List<E> sortedList = new ArrayList<>();
while (!heap.isEmpty()) {
sortedList.add(heap.poll());
}
return sortedList;
}
2.2 求第 K 大的数
求第 K 大的数是一个常见的面试题,利用 PriorityQueue
可以高效地解决这个问题:
java
public static int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
return minHeap.peek();
}
2.3 带权图的遍历
在带权图的遍历算法(如 Dijkstra 算法)中,PriorityQueue
可以用于高效地选择当前距离最近的顶点:
java
public static Map<Vertex, Integer> dijkstra(Graph graph, Vertex source) {
PriorityQueue<Vertex> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(Vertex::getDistance));
source.setDistance(0);
priorityQueue.add(source);
Map<Vertex, Integer> distances = new HashMap<>();
while (!priorityQueue.isEmpty()) {
Vertex current = priorityQueue.poll();
for (Edge edge : current.getEdges()) {
Vertex neighbor = edge.getTarget();
int newDist = current.getDistance() + edge.getWeight();
if (newDist < neighbor.getDistance()) {
neighbor.setDistance(newDist);
priorityQueue.add(neighbor);
distances.put(neighbor, newDist);
}
}
}
return distances;
}
3. 注意事项
PriorityQueue
是非线程安全的,如果需要在多线程环境中使用,请考虑使用PriorityBlockingQueue
。PriorityQueue
不支持存储NULL
元素。PriorityQueue
默认是小顶堆,如果需要自定义优先级顺序,可以提供一个Comparator
。