目录
[1.1 基本概念](#1.1 基本概念)
[1.2 核心操作](#1.2 核心操作)
[2.1 堆的定义](#2.1 堆的定义)
[2.2 堆的存储](#2.2 堆的存储)
[2.3 堆的核心操作](#2.3 堆的核心操作)
[2.3.1 向下调整(Shift Down)](#2.3.1 向下调整(Shift Down))
[2.3.2 向上调整(Shift Up)](#2.3.2 向上调整(Shift Up))
[2.3.3 建堆(Heapify)](#2.3.3 建堆(Heapify))
[2.4 堆的插入与删除](#2.4 堆的插入与删除)
[三、Java 中的 PriorityQueue](#三、Java 中的 PriorityQueue)
[3.1 基本特性](#3.1 基本特性)
[3.2 常用构造方法](#3.2 常用构造方法)
[3.3 自定义比较器实现大根堆](#3.3 自定义比较器实现大根堆)
[3.4 扩容机制](#3.4 扩容机制)
[4.1 堆排序(Heap Sort)](#4.1 堆排序(Heap Sort))
[4.2 Top-K 问题](#4.2 Top-K 问题)
前言
在计算机科学中,队列(Queue) 是一种经典的先进先出(FIFO)数据结构。然而,许多实际场景需要根据优先级处理元素,而非单纯遵循先后顺序。例如:
-
手机游戏中,来电通知应优先于游戏逻辑处理;
-
学生选座位时,成绩优异的同学可能拥有优先选择权。
为此,优先级队列(Priority Queue) 应运而生。它支持高效获取最高优先级元素与插入新元素,是许多高级算法与系统的核心组件。本文将系统讲解优先级队列的实现原理------堆(Heap) ,并结合 Java 中的 PriorityQueue 探讨其实际应用。
一、优先级队列概述
1.1 基本概念
优先级队列是一种抽象数据类型,其元素带有优先级,出队时优先级最高(或最低)的元素先被取出。它不遵循严格的 FIFO 规则,而是依据优先级动态调度。

1.2 核心操作
offer(E e):插入元素
poll():移除并返回优先级最高的元素
peek():查看优先级最高的元素(不移除)
二、堆:优先级队列的底层实现
2.1 堆的定义
堆是一种完全二叉树,满足以下性质之一:
-
大根堆(Max Heap) :每个节点的值 ≥ 其子节点的值

-
小根堆(Min Heap) :每个节点的值 ≤ 其子节点的值

数学定义 :
对于数组存储的堆,若下标从 0 开始,则满足:
大根堆:Ki≥K2i+1Ki≥K2i+1 且 Ki≥K2i+2Ki≥K2i+2
小根堆:Ki≤K2i+1Ki≤K2i+1 且 Ki≤K2i+2Ki≤K2i+2
2.2 堆的存储
由于堆是完全二叉树,可采用数组顺序存储,节省空间且支持快速定位:
父节点下标:
parent(i) = (i - 1) / 2左子节点下标:
left(i) = 2*i + 1右子节点下标:
right(i) = 2*i + 2
2.3 堆的核心操作
2.3.1 向下调整(Shift Down)
用于删除堆顶或重建堆。从指定节点开始,与其较小(小堆)或较大(大堆)的子节点交换,直至满足堆性质。
java
public void shiftDown(int[] array, int parent) {
int child = 2 * parent + 1;
int size = array.length;
while (child < size) {
if (child + 1 < size && array[child + 1] < array[child]) {
child++;
}
if (array[parent] <= array[child]) break;
swap(array, parent, child);
parent = child;
child = 2 * parent + 1;
}
}
时间复杂度:O(logn)
2.3.2 向上调整(Shift Up)
用于插入元素。将新节点与其父节点比较,若违反堆性质则交换,直至根节点。
java
public void shiftUp(int[] array, int child) {
int parent = (child - 1) / 2;
while (child > 0 && array[parent] > array[child]) {
swap(array, parent, child);
child = parent;
parent = (child - 1) / 2;
}
}
2.3.3 建堆(Heapify)
从最后一个非叶子节点开始,向前依次执行向下调整:
java
public void createHeap(int[] array) {
for (int i = (array.length - 2) / 2; i >= 0; i--) {
shiftDown(array, i);
}
}
建堆时间复杂度:O(n)O(n)(通过错位相减法可严格证明)
2.4 堆的插入与删除
-
插入:将元素放至末尾,执行向上调整
-
删除堆顶:将堆顶与末尾元素交换,删除末尾,再向下调整堆顶
三、Java 中的 PriorityQueue
3.1 基本特性
PriorityQueue是 Java 集合框架提供的优先级队列实现:
基于堆实现,默认小根堆
线程不安全(线程安全版本:
PriorityBlockingQueue)不允许
null元素,元素必须可比较动态扩容,无容量限制
3.2 常用构造方法
java
// 默认小堆,初始容量11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 指定初始容量
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
// 通过集合初始化
List<Integer> list = Arrays.asList(4, 3, 2, 1);
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
3.3 自定义比较器实现大根堆
java
class IntCmp implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 降序排列
}
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());
3.4 扩容机制
-
容量 < 64:扩容为原来的 2 倍
-
容量 ≥ 64:扩容为原来的 1.5 倍
-
最大容量为
Integer.MAX_VALUE - 8
四、堆的应用
4.1 堆排序(Heap Sort)
步骤:
-
建堆:将无序数组构建成堆(升序建大堆,降序建小堆)
-
排序:重复将堆顶元素(最大/最小)与末尾交换,然后调整堆
时间复杂度 :O(nlogn)
空间复杂度:O(1)(原地排序)
4.2 Top-K 问题
高效求解前 K 个最大或最小元素:
算法步骤(求前 K 个最小元素为例):
用前 K 个元素构建一个大根堆
遍历剩余元素,若当前元素小于堆顶,则替换堆顶并调整堆
遍历完成后,堆中即为最小的 K 个元素
时间复杂度 :O(nlogK)
空间复杂度:O(K)
java
public int[] smallestK(int[] arr, int k) {
if (k <= 0) return new int[0];
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
for (int num : arr) {
if (maxHeap.size() < k) {
maxHeap.offer(num);
} else if (num < maxHeap.peek()) {
maxHeap.poll();
maxHeap.offer(num);
}
}
int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = maxHeap.poll();
}
return res;
}
总结
优先级队列以其高效的动态优先级管理能力,广泛应用于操作系统调度、网络流量管理、实时系统等领域。堆作为其核心实现,结合了完全二叉树的结构优势与数组存储的空间效率,使得插入、删除均能在对数时间内完成。
掌握堆与优先级队列,不仅是学习数据结构的关键一步,更是解决 Top-K、流数据中位数、Dijkstra 算法等高级问题的基础。在实际开发中,合理选择 PriorityQueue 并理解其底层机制,能显著提升程序的性能与可维护性。