1. 什么是二叉堆
二叉堆(Binary Heap)是一种特殊的完全二叉树数据结构,它满足堆性质:对于最大堆,每个节点的值都大于或等于其子节点的值;对于最小堆,每个节点的值都小于或等于其子节点的值。二叉堆通常用于实现优先队列。
2. 二叉堆的特性
- 完全二叉树:除了最后一层,其他层都是满的,且最后一层的节点都靠左排列。
- 堆序性:最大堆中父节点值 ≥ 子节点值;最小堆中父节点值 ≤ 子节点值。
- 数组表示:二叉堆通常用数组存储,节省指针空间,且父子节点索引关系明确。
3. 数组表示与索引关系
对于存储在数组中的二叉堆(索引从0开始):
- 父节点索引:
parent(i) = (i - 1) / 2 - 左子节点索引:
left(i) = 2 * i + 1 - 右子节点索引:
right(i) = 2 * i + 2
4. 核心操作
4.1 上浮(Heapify Up)
当在堆尾插入新元素时,需要将其与父节点比较,如果违反堆性质则交换,直到满足堆性质为止。
4.2 下沉(Heapify Down)
当删除堆顶元素(通常将堆尾元素移到堆顶)时,需要将其与子节点比较,如果违反堆性质则与较大的子节点(最大堆)或较小的子节点(最小堆)交换,直到满足堆性质。
4.3 插入元素
将新元素添加到数组末尾,然后执行上浮操作。
4.4 删除堆顶
将堆顶元素与最后一个元素交换,删除最后一个元素(原堆顶),然后对新的堆顶执行下沉操作。
4.5 建堆
从一个无序数组构建堆:从最后一个非叶子节点开始,向前遍历,对每个节点执行下沉操作。
5. 代码实现(Java)
public class MaxHeap { private int[] heap; private int size; private int capacity; public MaxHeap(int capacity) { this.capacity = capacity; this.heap = new int[capacity]; this.size = 0; } // 获取父节点索引 private int parent(int i) { return (i - 1) / 2; } // 获取左子节点索引 private int leftChild(int i) { return 2 * i + 1; } // 获取右子节点索引 private int rightChild(int i) { return 2 * i + 2; } // 交换元素 private void swap(int i, int j) { int temp = heap[i]; heap[i] = heap[j]; heap[j] = temp; } // 上浮操作 private void heapifyUp(int i) { while (i > 0 && heap[parent(i)] < heap[i]) { swap(i, parent(i)); i = parent(i); } } // 下沉操作 private void heapifyDown(int i) { int maxIndex = i; int left = leftChild(i); int right = rightChild(i); if (left < size && heap[left] > heap[maxIndex]) { maxIndex = left; } if (right < size && heap[right] > heap[maxIndex]) { maxIndex = right; } if (i != maxIndex) { swap(i, maxIndex); heapifyDown(maxIndex); } } // 插入元素 public void insert(int value) { if (size == capacity) { throw new IllegalStateException("Heap is full"); } heap[size] = value; heapifyUp(size); size++; } // 删除堆顶元素 public int extractMax() { if (size == 0) { throw new IllegalStateException("Heap is empty"); } int result = heap[0]; heap[0] = heap[size - 1]; size--; heapifyDown(0); return result; } // 建堆 public void buildHeap(int[] array) { if (array.length > capacity) { throw new IllegalArgumentException("Array too large"); } System.arraycopy(array, 0, heap, 0, array.length); size = array.length; // 从最后一个非叶子节点开始下沉 for (int i = size / 2 - 1; i >= 0; i--) { heapifyDown(i); } } // 获取堆顶元素(不删除) public int peek() { if (size == 0) { throw new IllegalStateException("Heap is empty"); } return heap[0]; } public int size() { return size; } public boolean isEmpty() { return size == 0; } }6. 时间复杂度分析
- 插入:O(log n) - 上浮操作最多需要 log n 次比较
- 删除堆顶:O(log n) - 下沉操作最多需要 log n 次比较
- 建堆:O(n) - 看似 O(n log n),但通过数学分析可得 O(n)
- 获取堆顶:O(1)
7. 应用场景
- 优先队列:二叉堆是优先队列的高效实现方式
- 堆排序:基于二叉堆的排序算法,时间复杂度 O(n log n)
- Top K 问题:使用最小堆维护最大的 K 个元素
- Dijkstra 算法:用于寻找最短路径时维护待处理节点
- 哈夫曼编码:构建哈夫曼树时使用优先队列
8. 二叉堆 vs 二叉搜索树
| 特性 | 二叉堆 | 二叉搜索树 |
|---|---|---|
| 主要用途 | 快速获取最大/最小值 | 快速查找、插入、删除任意元素 |
| 时间复杂度 | 获取最值 O(1),插入删除 O(log n) | 查找、插入、删除平均 O(log n) |
| 有序性 | 只保证堆性质,不完全有序 | 中序遍历有序 |
| 实现复杂度 | 简单,数组存储 | 相对复杂,需要指针 |
9. 总结
二叉堆是一种简单高效的数据结构,特别适合需要频繁获取最大或最小元素的场景。它的数组表示形式节省空间,核心操作(上浮、下沉)的时间复杂度为 O(log n),是优先队列的标准实现方式。掌握二叉堆对于理解更高级的数据结构和算法(如堆排序、图算法)具有重要意义。