最小/大堆的实现
第一部分:堆的基本概念
1.1 什么是堆?
-
完全二叉树
-
堆序性质:最小堆/最大堆
-
数组表示法:
parent = (i-1)/2,left = 2*i+1,right = 2*i+2
1.2 堆的两种类型
最小堆:父节点 ≤ 子节点
最大堆:父节点 ≥ 子节点
1.3 时间复杂度分析
| 操作 | 时间复杂度 |
|---|---|
| 插入 | O(log n) |
| 删除堆顶 | O(log n) |
| 获取堆顶 | O(1) |
| 建堆 | O(n) |
第二部分:最小堆的完整实现
2.1 类定义和私有成员
class MinHeap {
private:
vector<int> heap; // 存储堆元素的数组
int size; // 当前堆的大小
int capacity; // 堆的容量
// 辅助函数声明
int parent(int idx) { return (idx - 1) / 2; }
int leftChild(int idx) { return 2 * idx + 1; }
int rightChild(int idx) { return 2 * idx + 2; }
// 核心操作
void heapifyUp(int idx); // 从下向上调整
void heapifyDown(int idx); // 从上向下调整
void buildHeap(); // 原地建堆
2.2 核心操作实现
// 从下向上调整(插入时使用)
void MinHeap::heapifyUp(int idx) {
while (idx > 0 && heap[idx] < heap[parent(idx)]) {
swap(heap[idx], heap[parent(idx)]);
idx = parent(idx);
}
}
// 从上向下调整(删除堆顶时使用)
void MinHeap::heapifyDown(int idx) {
int smallest = idx;
int left = leftChild(idx);
int right = rightChild(idx);
// 找出当前节点和两个子节点中最小的
if (left < size && heap[left] < heap[smallest]) {
smallest = left;
}
if (right < size && heap[right] < heap[smallest]) {
smallest = right;
}
// 如果最小的不是当前节点,交换并继续向下调整
if (smallest != idx) {
swap(heap[idx], heap[smallest]);
heapifyDown(smallest); // 递归版本
// heapifyDownIterative(smallest); // 也可以写迭代版本
}
}
// 迭代版本(可选,展示不同写法)
void MinHeap::heapifyDownIterative(int idx) {
while (true) {
int smallest = idx;
int left = leftChild(idx);
int right = rightChild(idx);
if (left < size && heap[left] < heap[smallest]) smallest = left;
if (right < size && heap[right] < heap[smallest]) smallest = right;
if (smallest == idx) break;
swap(heap[idx], heap[smallest]);
idx = smallest;
}
}
2.3 公有接口
public:
// 构造函数
MinHeap() : capacity(10), size(0) {
heap.resize(capacity);
}
explicit MinHeap(int cap) : capacity(cap), size(0) {
heap.resize(capacity);
}
// 从数组构造堆(Floyd建堆法,O(n))
MinHeap(const vector<int>& arr) {
heap = arr;
size = heap.size();
capacity = size;
buildHeap();
}
// 插入元素
void push(int val) {
if (size == capacity) {
capacity *= 2;
heap.resize(capacity);
}
heap[size] = val;
heapifyUp(size);
size++;
}
// 删除堆顶元素
int pop() {
if (size == 0) {
throw runtime_error("Heap is empty");
}
int root = heap[0];
heap[0] = heap[size - 1];
size--;
heapifyDown(0);
return root;
}
// 获取堆顶元素
int top() const {
if (size == 0) throw runtime_error("Heap is empty");
return heap[0];
}
// 堆是否为空
bool empty() const { return size == 0; }
// 当前堆大小
int getSize() const { return size; }
// 清空堆
void clear() { size = 0; }
2.4 Floyd建堆算法(优化)
private:
void buildHeap() {
// 从最后一个非叶子节点开始,向前逐个调整
for (int i = size / 2 - 1; i >= 0; i--) {
heapifyDown(i);
}
}
第三部分:HeapifyUp 和 HeapifyDown 功能演示
3.1 HeapifyUp 示例(插入新元素时使用)
场景:向最小堆插入值 2
初始最小堆:
3
/ \
5 8
/ \ \
10 15 20
数组:[3, 5, 8, 10, 15, 20]
步骤1:插入新元素 2
3
/ \
5 8
/ \ \
10 15 20
/
2 ← 新元素插入在最后一个位置
数组:[3, 5, 8, 10, 15, 20, 2]
步骤2:HeapifyUp 开始
当前节点:2(索引6),父节点:10(索引3)
比较:2 < 10 ✓,需要交换
交换后:
3
/ \
5 8
/ \ \
2 15 20
/
10
数组:[3, 5, 8, 2, 15, 20, 10]
步骤3:继续向上比较
当前节点:2(索引3),父节点:5(索引1)
比较:2 < 5 ✓,需要交换
交换后:
3
/ \
2 8
/ \ \
5 15 20
/
10
数组:[3, 2, 8, 5, 15, 20, 10]
步骤4:继续向上比较
当前节点:2(索引1),父节点:3(索引0)
比较:2 < 3 ✓,需要交换
交换后:
2
/ \
3 8
/ \ \
5 15 20
/
10
数组:[2, 3, 8, 5, 15, 20, 10]
步骤5:到达根节点,停止
最终堆满足最小堆性质:
2
/ \
3 8
/ \ \
5 15 20
/
10
3.2 HeapifyDown 示例(删除堆顶时使用)
场景:删除最小堆的堆顶元素
初始最小堆:
2
/ \
3 8
/ \ \
5 15 20
/
10
数组:[2, 3, 8, 5, 15, 20, 10]
步骤1:删除堆顶元素 2
用最后一个元素 10 替换堆顶:
10 ← 原来在最后的元素
/ \
3 8
/ \ \
5 15 20
数组:[10, 3, 8, 5, 15, 20] (删除了最后一个元素)
步骤2:HeapifyDown 开始
当前节点:10(索引0)
比较三个节点:10、左子节点3、右子节点8
找到最小的是 3
步骤3:交换 10 和 3
交换后:
3
/ \
10 8
/ \ \
5 15 20
数组:[3, 10, 8, 5, 15, 20]
步骤4:继续向下调整
当前节点:10(索引1)
比较三个节点:10、左子节点5、右子节点15
找到最小的是 5
步骤5:交换 10 和 5
交换后:
3
/ \
5 8
/ \ \
10 15 20
数组:[3, 5, 8, 10, 15, 20]
步骤6:继续检查
当前节点:10(索引3)
左子节点:不存在(超出数组范围)
右子节点:不存在
到达叶子节点,停止
最终堆:
3
/ \
5 8
/ \ \
10 15 20
数组:[3, 5, 8, 10, 15, 20] ✓ 满足最小堆性质
记忆tips
HeapifyUp(上浮):
想象一下向水中扔一个乒乓球,如果它比周围的水轻(值小),就会向上浮,直到找到合适的位置。
HeapifyDown(下沉):
想象一下一块石头掉进水里,如果它比周围的水重(值大),就会向下沉,直到找到合适的位置。

浙公网安备 33010602011771号