一、堆的核心概念体系
1. 堆的定义与性质
Matlab
graph TB
ROOT((最大堆)) --> A[父节点 ≥ 子节点]
ROOT --> B[完全二叉树结构]
ROOT --> C[数组存储]
ROOT --> D[快速获取极值]
2. 堆类型对比
类型 | 特性 | 典型应用场景 |
---|---|---|
最大堆 | 父节点值 ≥ 子节点值 | 获取前K大元素 |
最小堆 | 父节点值 ≤ 子节点值 | 获取前K小元素 |
斐波那契堆 | 平摊O(1)时间复杂度的操作 | 图算法优化 |
二、堆的存储与操作原理
1. 数组存储结构
索引计算规则(下标从0开始):
-
父节点:
(i-1)/2
-
左子节点:
2i+1
-
右子节点:
2i+2
示例数组 :
[9, 7, 5, 6, 3, 1, 4]
对应堆结构:
Matlab
9
/ \
7 5
/ \ / \
6 3 1 4
2. 核心操作时间复杂度
操作 | 时间复杂度 | 说明 |
---|---|---|
插入元素 | O(log n) | 上浮(swim)操作 |
删除堆顶 | O(log n) | 下沉(sink)操作 |
获取极值 | O(1) | 直接访问根节点 |
构建堆 | O(n) | Floyd算法自底向上构建 |
三、Java标准库实现:PriorityQueue
1. 使用示例
java
// 最小堆(默认)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// 最大堆(使用反向比较器)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
// 自定义对象堆
PriorityQueue<Student> stuHeap = new PriorityQueue<>(
Comparator.comparingInt(Student::getScore)
);
2. 源码关键实现
java
// 底层数组存储
transient Object[] queue;
// 上浮操作(插入时)
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
// 下沉操作(删除时)
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
四、手动实现堆结构
1. 最小堆完整实现
java
public class MinHeap {
private int[] heap;
private int size;
private static final int DEFAULT_CAPACITY = 10;
public MinHeap() {
this(DEFAULT_CAPACITY);
}
public MinHeap(int capacity) {
heap = new int[capacity];
}
// 插入操作
public void insert(int value) {
if (size == heap.length) resize();
heap[size] = value;
swim(size);
size++;
}
// 删除堆顶
public int extractMin() {
if (size == 0) throw new IllegalStateException();
int min = heap[0];
heap[0] = heap[size-1];
size--;
sink(0);
return min;
}
// 上浮操作
private void swim(int k) {
while (k > 0 && heap[k] < heap[(k-1)/2]) {
swap(k, (k-1)/2);
k = (k-1)/2;
}
}
// 下沉操作
private void sink(int k) {
while (2*k+1 < size) {
int j = 2*k+1;
if (j+1 < size && heap[j+1] < heap[j]) j++;
if (heap[k] <= heap[j]) break;
swap(k, j);
k = j;
}
}
// 扩容机制
private void resize() {
heap = Arrays.copyOf(heap, heap.length * 2);
}
}
五、堆排序算法实现
1. 排序步骤
java
public static void heapSort(int[] arr) {
// 构建最大堆
int n = arr.length;
for (int i = n/2-1; i >= 0; i--) {
heapify(arr, n, i);
}
// 逐个提取元素
for (int i = n-1; i > 0; i--) {
swap(arr, 0, i);
heapify(arr, i, 0);
}
}
private static void heapify(int[] arr, int n, int i) {
int largest = i;
int l = 2*i+1;
int r = 2*i+2;
if (l < n && arr[l] > arr[largest]) largest = l;
if (r < n && arr[r] > arr[largest]) largest = r;
if (largest != i) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
2. 性能特点
-
时间复杂度:O(n log n)
-
空间复杂度:O(1)(原地排序)
-
不稳定排序算法
六、堆的实际应用场景
1. Top K问题
求前K大元素:
java
public List<Integer> topKElements(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
return new ArrayList<>(minHeap);
}
2. 合并K个有序链表
java
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> heap = new PriorityQueue<>(
Comparator.comparingInt(n -> n.val)
);
for (ListNode node : lists) {
if (node != null) heap.offer(node);
}
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!heap.isEmpty()) {
ListNode min = heap.poll();
curr.next = min;
curr = curr.next;
if (min.next != null) {
heap.offer(min.next);
}
}
return dummy.next;
}
七、高级应用与优化
1. 动态数据流的中位数
java
class MedianFinder {
PriorityQueue<Integer> minHeap; // 存储较大的一半
PriorityQueue<Integer> maxHeap; // 存储较小的一半
public MedianFinder() {
minHeap = new PriorityQueue<>();
maxHeap = new PriorityQueue<>(Collections.reverseOrder());
}
public void addNum(int num) {
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
if (maxHeap.size() < minHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
return maxHeap.size() > minHeap.size()
? maxHeap.peek()
: (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
2. 定时任务调度
java
class Scheduler {
private PriorityQueue<Task> taskQueue = new PriorityQueue<>(
Comparator.comparingLong(Task::getExecuteTime)
);
public void addTask(Task task) {
taskQueue.offer(task);
}
public void run() {
while (!taskQueue.isEmpty()) {
Task task = taskQueue.poll();
long current = System.currentTimeMillis();
if (task.getExecuteTime() > current) {
try {
Thread.sleep(task.getExecuteTime() - current);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
task.execute();
}
}
}
八、常见问题与解决方案
1. 如何选择堆的类型?
场景 | 推荐堆类型 |
---|---|
需要快速获取最大值 | 最大堆 |
高频插入与删除最小值 | 最小堆 |
数据动态变化的中位数计算 | 双堆组合 |
2. 线程安全问题处理方案
java
// 使用并发堆实现
PriorityBlockingQueue<Integer> safeHeap = new PriorityBlockingQueue<>();
// 手动同步
PriorityQueue<Integer> heap = new PriorityQueue<>();
synchronized(heap) {
heap.offer(123);
// 其他操作
}
九、性能调优技巧
1. 堆初始化优化
java
// 预估数据量大小,避免频繁扩容
int expectedSize = 100000;
PriorityQueue<Integer> heap = new PriorityQueue<>(expectedSize);
2. 对象池技术减少GC压力
java
class ObjectPool {
private PriorityQueue<ReusableObject> pool = new PriorityQueue<>(
Comparator.comparingInt(o -> o.priority)
);
public ReusableObject getObject() {
return pool.poll() ?? createNewObject();
}
public void returnObject(ReusableObject obj) {
obj.resetState();
pool.offer(obj);
}
}
十、总结与选型建议
堆的核心优势
-
极值访问高效:O(1)时间复杂度获取最大/最小值
-
动态数据管理:持续插入/删除操作保持高效
-
内存紧凑:数组存储相比链表更节省空间
使用注意事项
-
不支持快速查找:任意元素查找需要O(n)
-
非线程安全:多线程环境需使用并发版本
-
比较器陷阱:自定义比较器需确保逻辑正确
选型决策树:
Matlab
需要管理动态数据集?
├── 是 → 需要频繁获取极值?
│ ├── 是 → 使用堆结构
│ └── 否 → 考虑哈希表或平衡树
└── 否 → 使用普通数组
扩展方向:
-
研究斐波那契堆等高级堆结构
-
探索堆在机器学习中的应用(如优先级经验回放)
-
结合堆外内存实现超大堆结构
通过对堆结构的深入理解和合理应用,开发者可以显著提升系统在处理优先级任务、实时数据流分析等场景下的性能表现。