一、优先级队列概述
1.1 基本概念
优先级队列是一种特殊的队列数据结构,其中每个元素都关联一个"优先级"。与普通队列的先进先出(FIFO)不同,优先级队列按照元素的优先级进行出队操作,优先级高的元素先出队。
1.2 Java中的PriorityQueue
Java提供了PriorityQueue类,它实现了Queue接口,底层基于堆数据结构实现。
java
java
import java.util.PriorityQueue;
import java.util.Queue;
public class PriorityQueueDemo {
public static void main(String[] args) {
// 默认创建小根堆(最小堆)
Queue<Integer> minHeap = new PriorityQueue<>();
minHeap.offer(28);
minHeap.offer(23);
minHeap.offer(19);
minHeap.offer(21);
minHeap.offer(8);
minHeap.offer(18);
minHeap.offer(5);
System.out.println("小根堆内容:");
while (!minHeap.isEmpty()) {
System.out.print(minHeap.poll() + " ");
}
// 输出: 5 8 18 19 21 23 28
}
}
二、堆数据结构详解
2.1 堆的基本概念
堆是一种特殊的完全二叉树,满足以下性质:
- 大根堆:每个节点的值都大于或等于其子节点的值
- 小根堆:每个节点的值都小于或等于其子节点的值
2.2 堆的数组表示
由于堆是完全二叉树,可以用数组高效存储:
- 父节点下标:
(i-1)/2 - 左子节点:
2*i + 1 - 右子节点:
2*i + 2
2.3 创建自定义堆类型
java
java
import java.util.Comparator;
// 自定义比较器创建大根堆
class MaxHeapComparator implements Comparator<Integer> {
@Override
public int compare(Integer a, Integer b) {
return b - a; // 降序排列,创建大根堆
}
}
public class CustomHeapDemo {
public static void main(String[] args) {
// 使用自定义比较器创建大根堆
Queue<Integer> maxHeap = new PriorityQueue<>(new MaxHeapComparator());
maxHeap.offer(28);
maxHeap.offer(23);
maxHeap.offer(19);
maxHeap.offer(21);
maxHeap.offer(8);
maxHeap.offer(18);
maxHeap.offer(5);
System.out.println("大根堆内容:");
while (!maxHeap.isEmpty()) {
System.out.print(maxHeap.poll() + " ");
}
// 输出: 28 23 21 19 18 8 5
}
}
三、堆的核心操作实现
3.1 向下调整算法
大根堆向下调整
java
ini
/**
* 大根堆向下调整
* @param root 需要调整的根节点下标
* @param len 堆的有效长度
*/
private void shiftDownBig(int root, int len) {
int parent = root;
int child = 2 * parent + 1; // 左孩子
while (child < len) {
// 选择左右孩子中较大的那个
if (child + 1 < len && elem[child] < elem[child + 1]) {
child++; // 右孩子更大
}
// 如果孩子大于父亲,需要交换
if (elem[child] > elem[parent]) {
swap(parent, child);
parent = child;
child = 2 * parent + 1;
} else {
break; // 调整完成
}
}
}
/**
* 小根堆向下调整
*/
private void shiftDownSmall(int root, int len) {
int parent = root;
int child = 2 * parent + 1;
while (child < len) {
// 选择左右孩子中较小的那个
if (child + 1 < len && elem[child] > elem[child + 1]) {
child++;
}
// 如果孩子小于父亲,需要交换
if (elem[child] < elem[parent]) {
swap(parent, child);
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
private void swap(int i, int j) {
int temp = elem[i];
elem[i] = elem[j];
elem[j] = temp;
}
向下调整过程图解
text
makefile
初始大根堆(需要调整节点24):
67
/ \
45 12
/ \ / \
24 29 8 10
/ \
19 21
步骤1: 24与较大的孩子45比较,24 < 45,交换
67
/ \
45 12
/ \ / \
45 29 8 10 ← 24移动到45的位置
/ \
19 21
步骤2: 24与较大的孩子29比较,24 < 29,交换
67
/ \
45 12
/ \ / \
29 29 8 10 ← 24移动到29的位置
/ \
19 21
最终: 24找到合适位置
67
/ \
45 12
/ \ / \
29 24 8 10
/ \
19 21
3.2 向上调整算法
大根堆向上调整
java
ini
/**
* 大根堆向上调整
* @param child 需要调整的孩子节点下标
*/
private void shiftUpBig(int child) {
int parent = (child - 1) / 2;
while (child > 0) {
// 如果孩子大于父亲,需要交换
if (elem[child] > elem[parent]) {
swap(child, parent);
child = parent;
parent = (child - 1) / 2;
} else {
break; // 调整完成
}
}
}
/**
* 小根堆向上调整
*/
private void shiftUpSmall(int child) {
int parent = (child - 1) / 2;
while (child > 0) {
// 如果孩子小于父亲,需要交换
if (elem[child] < elem[parent]) {
swap(child, parent);
child = parent;
parent = (child - 1) / 2;
} else {
break;
}
}
}
向上调整过程图解
text
makefile
初始(需要调整节点24):
67
/ \
45 12
/ \ / \
29 19 8 10
/ \
21 24
步骤1: 24与直接父亲21比较,24 > 21,交换
67
/ \
45 12
/ \ / \
29 19 8 10
/ \
24 21
步骤2: 24与父亲29比较,24 < 29,停止
67
/ \
45 12
/ \ / \
29 19 8 10
/ \
24 21
四、堆的构建方法
4.1 向下调整建堆
java
ini
public class HeapBuilder {
private int[] elem;
/**
* 向下调整构建大根堆
* 时间复杂度: O(n)
*/
public void buildMaxHeapDown(int[] array) {
this.elem = array.clone();
int n = elem.length;
// 从最后一个非叶子节点开始向下调整
for (int i = (n - 2) / 2; i >= 0; i--) {
shiftDownBig(i, n);
}
}
/**
* 向下调整构建小根堆
*/
public void buildMinHeapDown(int[] array) {
this.elem = array.clone();
int n = elem.length;
for (int i = (n - 2) / 2; i >= 0; i--) {
shiftDownSmall(i, n);
}
}
}
4.2 向上调整建堆
java
ini
public class HeapBuilder {
/**
* 向上调整构建大根堆
* 时间复杂度: O(n log n)
*/
public void buildMaxHeapUp(int[] array) {
this.elem = array.clone();
int n = elem.length;
// 从第二个元素开始向上调整
for (int i = 1; i < n; i++) {
shiftUpBig(i);
}
}
/**
* 向上调整构建小根堆
*/
public void buildMinHeapUp(int[] array) {
this.elem = array.clone();
int n = elem.length;
for (int i = 1; i < n; i++) {
shiftUpSmall(i);
}
}
}
五、完整堆实现
5.1 自定义堆类
java
ini
import java.util.Arrays;
import java.util.NoSuchElementException;
public class MyPriorityQueue {
private int[] elem; // 存储堆元素的数组
private int size; // 堆中元素个数
private boolean isMaxHeap; // 标记是否为大根堆
private static final int DEFAULT_CAPACITY = 10;
// 构造方法
public MyPriorityQueue() {
this(false); // 默认小根堆
}
public MyPriorityQueue(boolean isMaxHeap) {
this.elem = new int[DEFAULT_CAPACITY];
this.size = 0;
this.isMaxHeap = isMaxHeap;
}
public MyPriorityQueue(int[] array, boolean isMaxHeap) {
this.isMaxHeap = isMaxHeap;
buildHeap(array);
}
// 构建堆
private void buildHeap(int[] array) {
this.elem = array.clone();
this.size = array.length;
// 使用向下调整建堆(效率更高)
for (int i = (size - 2) / 2; i >= 0; i--) {
shiftDown(i);
}
}
// 统一的向下调整方法
private void shiftDown(int root) {
if (isMaxHeap) {
shiftDownBig(root, size);
} else {
shiftDownSmall(root, size);
}
}
// 统一的向上调整方法
private void shiftUp(int child) {
if (isMaxHeap) {
shiftUpBig(child);
} else {
shiftUpSmall(child);
}
}
// 插入元素
public void offer(int val) {
// 扩容检查
if (size >= elem.length) {
elem = Arrays.copyOf(elem, elem.length * 2);
}
elem[size] = val;
shiftUp(size);
size++;
}
// 删除堆顶元素
public int poll() {
if (isEmpty()) {
throw new NoSuchElementException("Heap is empty");
}
int result = elem[0];
// 将最后一个元素移到堆顶
elem[0] = elem[size - 1];
size--;
// 从堆顶开始向下调整
if (size > 0) {
shiftDown(0);
}
return result;
}
// 获取堆顶元素(不删除)
public int peek() {
if (isEmpty()) {
throw new NoSuchElementException("Heap is empty");
}
return elem[0];
}
// 堆大小
public int size() {
return size;
}
// 判断堆是否为空
public boolean isEmpty() {
return size == 0;
}
// 清空堆
public void clear() {
size = 0;
// 可选:释放数组引用
// elem = new int[DEFAULT_CAPACITY];
}
// 打印堆内容(调试用)
public void printHeap() {
System.out.print("Heap: ");
for (int i = 0; i < size; i++) {
System.out.print(elem[i] + " ");
}
System.out.println();
}
// 原有的调整方法(稍作修改)
private void shiftDownBig(int root, int len) {
int parent = root;
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && elem[child] < elem[child + 1]) {
child++;
}
if (elem[child] > elem[parent]) {
swap(parent, child);
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
private void shiftDownSmall(int root, int len) {
int parent = root;
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && elem[child] > elem[child + 1]) {
child++;
}
if (elem[child] < elem[parent]) {
swap(parent, child);
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
private void shiftUpBig(int child) {
int parent = (child - 1) / 2;
while (child > 0) {
if (elem[child] > elem[parent]) {
swap(child, parent);
child = parent;
parent = (child - 1) / 2;
} else {
break;
}
}
}
private void shiftUpSmall(int child) {
int parent = (child - 1) / 2;
while (child > 0) {
if (elem[child] < elem[parent]) {
swap(child, parent);
child = parent;
parent = (child - 1) / 2;
} else {
break;
}
}
}
private void swap(int i, int j) {
int temp = elem[i];
elem[i] = elem[j];
elem[j] = temp;
}
}
5.2 测试示例
java
csharp
public class HeapTest {
public static void main(String[] args) {
// 测试小根堆
System.out.println("=== 小根堆测试 ===");
MyPriorityQueue minHeap = new MyPriorityQueue(false);
int[] testData = {28, 23, 19, 21, 8, 18, 5};
for (int num : testData) {
minHeap.offer(num);
}
minHeap.printHeap(); // 堆结构
System.out.println("按优先级出队:");
while (!minHeap.isEmpty()) {
System.out.print(minHeap.poll() + " ");
}
System.out.println();
// 测试大根堆
System.out.println("\n=== 大根堆测试 ===");
MyPriorityQueue maxHeap = new MyPriorityQueue(true);
for (int num : testData) {
maxHeap.offer(num);
}
maxHeap.printHeap(); // 堆结构
System.out.println("按优先级出队:");
while (!maxHeap.isEmpty()) {
System.out.print(maxHeap.poll() + " ");
}
System.out.println();
// 测试从数组构建堆
System.out.println("\n=== 数组构建堆测试 ===");
int[] array = {3, 1, 4, 1, 5, 9, 2, 6};
MyPriorityQueue heapFromArray = new MyPriorityQueue(array, true);
heapFromArray.printHeap();
System.out.println("堆顶元素: " + heapFromArray.peek());
}
}
六、堆的应用场景
6.1 实际应用
- 任务调度:操作系统进程调度
- 数据流的中位数:维护两个堆(大根堆+小根堆)
- Dijkstra算法:图的最短路径
- Huffman编码:数据压缩
- 堆排序:高效的排序算法
6.2 堆排序实现
java
ini
public class HeapSort {
/**
* 堆排序(升序 - 使用大根堆)
*/
public static void heapSort(int[] arr) {
int n = arr.length;
// 构建大根堆
for (int i = (n - 2) / 2; i >= 0; i--) {
shiftDownBig(arr, i, n);
}
// 逐个提取最大元素
for (int i = n - 1; i > 0; i--) {
// 将堆顶元素(最大)与当前末尾元素交换
swap(arr, 0, i);
// 调整剩余元素为堆
shiftDownBig(arr, 0, i);
}
}
private static void shiftDownBig(int[] arr, int root, int len) {
int parent = root;
int child = 2 * parent + 1;
while (child < len) {
if (child + 1 < len && arr[child] < arr[child + 1]) {
child++;
}
if (arr[child] > arr[parent]) {
swap(arr, parent, child);
parent = child;
child = 2 * parent + 1;
} else {
break;
}
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
七、性能分析与总结
7.1 时间复杂度
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 插入元素 | O(log n) | 向上调整 |
| 删除堆顶 | O(log n) | 向下调整 |
| 获取堆顶 | O(1) | 直接访问 |
| 建堆 | O(n) | 向下调整建堆 |
7.2 核心要点总结
- 堆的性质:完全二叉树 + 堆序性质
- 数组存储:利用完全二叉树的特性高效存储
- 调整操作:向上调整用于插入,向下调整用于删除
- 建堆选择:向下调整建堆效率更高(O(n) vs O(n log n))
- 应用广泛:从操作系统到算法竞赛都有重要应用
通过深入理解堆的原理和实现,可以更好地应用这种高效的数据结构解决实际问题。