一、堆的本质与特性
1.1 什么是堆数据结构?
堆(Heap)是一种特殊的完全二叉树,它满足以下核心性质:
-
堆序性:每个节点的值都满足特定顺序关系
-
结构性:完全二叉树的结构特性(除最后一层外都是满的,最后一层节点左对齐)
这种数据结构由J.W.J. Williams在1964年提出,最初用于实现高效的堆排序算法。堆在计算机科学中的应用非常广泛,从操作系统内存管理到高级算法实现都有它的身影。
1.2 堆的两种基本类型
最大堆(Max-Heap):
-
父节点值 ≥ 子节点值
-
根节点是整个堆的最大值
-
典型应用:优先队列
最小堆(Min-Heap):
-
父节点值 ≤ 子节点值
-
根节点是整个堆的最小值
-
典型应用:定时任务调度
1.3 堆的底层实现方式
堆通常使用数组实现,其数组索引与树结构存在精确的数学对应关系:
数组实现的优势:
-
内存连续,缓存友好
-
索引计算高效(位运算优化)
-
完全二叉树的天然适配性
二、堆的核心操作与实现
2.1 元素插入(上浮操作)
插入操作的算法步骤:
-
将新元素添加到数组末尾
-
自底向上调整堆结构(上浮)
-
时间复杂度:O(log n)
void MaxHeap::insert(int key) { if (size >= capacity) { resize(2 * capacity);//扩容操作 } heap[size] = key; int current = size++; // 上浮操作-大堆 while (current != 0 && heap[parent(current)] < heap[current]) { //parent(current)对父亲节点的索引 swap(heap[current], heap[parent(current)]); current = parent(current); } }
2.2 元素删除(下沉操作)
删除根节点的算法步骤:
-
用最后一个元素替换根节点
-
自顶向下调整堆结构(下沉)
-
时间复杂度:O(log n)
int MaxHeap::extractMax() { if (size <= 0) return INT_MIN; if (size == 1) return heap[--size]; int root = heap[0]; heap[0] = heap[--size]; maxHeapify(0); return root; } void MaxHeap::maxHeapify(int i) {//递归调用 int l = left(i);//索引左孩子下标 int r = right(i);//索引右孩子下标 int largest = i; if (l < size && heap[l] > heap[i]) largest = l; if (r < size && heap[r] > heap[largest]) largest = r; if (largest != i) { swap(heap[i], heap[largest]); maxHeapify(largest); } }
2.3 堆的构建
两种建堆方式对比:
方法 时间复杂度 适用场景 连续插入法 O(n log n) 动态数据流 Floyd算法 O(n) 静态数组初始化 Floyd算法的实现技巧:
void MaxHeap::buildHeap() { // 从最后一个非叶子节点开始调整 int startIdx = (size/2) - 1; for (int i = startIdx; i >= 0; i--) { maxHeapify(i); } }
-
三、C/C++中的堆实现
3.1 手动实现堆结构(大堆)
#include <iostream>
#include <stdexcept>
#include <algorithm>
template <typename T>
class MaxHeap {
private:
T* heapArray; // 堆存储数组
int capacity; // 数组容量
int currentSize; // 当前元素数量
// 计算父节点索引
inline int parent(int i) const { return (i-1) >> 1; } // 用位运算优化除法
// 计算左子节点索引
inline int leftChild(int i) const { return (i << 1) + 1; }
// 计算右子节点索引
inline int rightChild(int i) const { return (i << 1) + 2; }
// 动态扩容(倍增策略)
void resize() {
int newCapacity = capacity == 0 ? 1 : capacity * 2;
T* newArray = new T[newCapacity];
// 迁移数据
for (int i = 0; i < currentSize; ++i) {
newArray[i] = heapArray[i];
}
delete[] heapArray;
heapArray = newArray;
capacity = newCapacity;
}
// 上浮操作
void siftUp(int index) {
while (index > 0 && heapArray[parent(index)] < heapArray[index]) {
std::swap(heapArray[parent(index)], heapArray[index]);
index = parent(index);
}
}
// 下沉操作
void siftDown(int index) {
int maxIndex = index;
int left = leftChild(index);
int right = rightChild(index);
// 找出三个节点中的最大值
if (left < currentSize && heapArray[left] > heapArray[maxIndex]) {
maxIndex = left;
}
if (right < currentSize && heapArray[right] > heapArray[maxIndex]) {
maxIndex = right;
}
// 如果最大值不是当前节点,交换并递归调整
if (maxIndex != index) {
std::swap(heapArray[index], heapArray[maxIndex]);
siftDown(maxIndex);
}
}
public:
// 构造函数
explicit MaxHeap(int initialCapacity = 10)
: capacity(initialCapacity), currentSize(0) {
if (initialCapacity <= 0) {
throw std::invalid_argument("Initial capacity must be positive");
}
heapArray = new T[capacity];
}
// 从数组构建堆(Floyd算法)
MaxHeap(T arr[], int size) : currentSize(size) {
capacity = size == 0 ? 1 : size;
heapArray = new T[capacity];
// 拷贝数据
for (int i = 0; i < size; ++i) {
heapArray[i] = arr[i];
}
// 从最后一个非叶子节点开始调整
for (int i = (size/2)-1; i >= 0; --i) {
siftDown(i);
}
}
// 析构函数
~MaxHeap() {
delete[] heapArray;
}
// 插入元素
void insert(T value) {
if (currentSize == capacity) {
resize();
}
heapArray[currentSize] = value;
siftUp(currentSize);
++currentSize;
}
// 提取最大值
T extractMax() {
if (isEmpty()) {
throw std::out_of_range("Heap is empty");
}
T max = heapArray[0];
heapArray[0] = heapArray[--currentSize];
siftDown(0);
return max;
}
// 获取最大值(不删除)
T getMax() const {
if (isEmpty()) {
throw std::out_of_range("Heap is empty");
}
return heapArray[0];
}
// 堆是否为空
bool isEmpty() const {
return currentSize == 0;
}
// 堆排序(会破坏堆结构)
static void heapSort(T arr[], int n) {
// 构建最大堆
for (int i = n/2 - 1; i >= 0; --i) {
MaxHeap<T>::siftDown(arr, n, i);
}
// 逐个提取元素
for (int i = n-1; i > 0; --i) {
std::swap(arr[0], arr[i]);
MaxHeap<T>::siftDown(arr, i, 0);
}
}
// 打印堆内容(调试用)
void print() const {
std::cout << "[";
for (int i = 0; i < currentSize; ++i) {
std::cout << heapArray[i];
if (i != currentSize-1) std::cout << ", ";
}
std::cout << "]" << std::endl;
}
private:
// 静态方法用于堆排序
static void siftDown(T arr[], int n, int i) {
int maxIndex = i;
int left = 2*i + 1;
int right = 2*i + 2;
if (left < n && arr[left] > arr[maxIndex]) {
maxIndex = left;
}
if (right < n && arr[right] > arr[maxIndex]) {
maxIndex = right;
}
if (maxIndex != i) {
std::swap(arr[i], arr[maxIndex]);
siftDown(arr, n, maxIndex);
}
}
};
// 测试用例
int main() {
try {
// 测试1:基本插入和提取
MaxHeap<int> heap;
heap.insert(3);
heap.insert(1);
heap.insert(4);
heap.insert(1);
heap.insert(5);
std::cout << "Test 1:" << std::endl;
heap.print(); // [5, 4, 3, 1, 1]
while (!heap.isEmpty()) {
std::cout << heap.extractMax() << " ";
} // 5 4 3 1 1
std::cout << "\n\n";
// 测试2:从数组构建堆
int arr[] = {2,7,4,1,8,1};
MaxHeap<int> heap2(arr, 6);
std::cout << "Test 2:" << std::endl;
heap2.print(); // [8, 7, 4, 1, 2, 1]
std::cout << "Max: " << heap2.getMax() << "\n\n"; // 8
// 测试3:堆排序
int sortArr[] = {9,3,2,5,1,4,8};
const int n = sizeof(sortArr)/sizeof(sortArr[0]);
MaxHeap<int>::heapSort(sortArr, n);
std::cout << "Test 3 (Heap Sort):" << std::endl;
for (int i = 0; i < n; ++i) {
std::cout << sortArr[i] << " "; // 1 2 3 4 5 8 9
}
std::cout << "\n\n";
// 测试4:异常处理
MaxHeap<int> emptyHeap;
try {
emptyHeap.extractMax();
} catch (const std::exception& e) {
std::cout << "Test 4 Exception: " << e.what() << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
3.2 STL中的priority_queue
标准库的使用示例:
#include <queue>
// 最小堆
priority_queue<int, vector<int>, greater<int>> minHeap;
// 最大堆(默认)
priority_queue<int> maxHeap;
// 自定义比较函数
struct Compare {
bool operator()(const Person& a, const Person& b) {
return a.age > b.age; // 年龄小的优先
}
};
priority_queue<Person, vector<Person>, Compare> customHeap;
四、堆的高级应用
4.1 堆排序算法
实现步骤:
-
构建最大堆
-
重复提取根节点并调整堆
-
时间复杂度:O(n log n)
void heapSort(int arr[], int n) { // 构建堆 for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i); // 逐个提取元素 for (int i = n-1; i > 0; i--) { swap(arr[0], arr[i]); heapify(arr, i, 0); } }
总结:升序建大堆,降序建小堆
4.2 海量数据Top K问题
使用堆的高效解法:
vector<int> getTopK(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int num : nums) {
if (minHeap.size() < k) {
minHeap.push(num);
} else if (num > minHeap.top()) {
minHeap.pop();
minHeap.push(num);
}
}
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top());
minHeap.pop();
}
return result;
}
五、堆的工程实践要点(了解)
5.1 内存管理最佳实践
-
动态数组扩容策略(倍增法)
-
异常安全处理
-
移动语义优化(C++11+)
5.2 性能优化技巧
-
缓存友好的内存布局
-
循环展开优化heapify
-
预分配内存策略
-
位运算优化索引计算
5.3 常见陷阱与调试技巧
-
数组越界问题
-
比较逻辑错误
-
内存泄漏检测
-
堆属性验证函数:
bool isMaxHeap(int arr[], int n, int i = 0) { if (i >= n) return true; int l = 2*i + 1; int r = 2*i + 2; bool valid = true; if (l < n) valid &= (arr[i] >= arr[l]); if (r < n) valid &= (arr[i] >= arr[r]); return valid && isMaxHeap(arr, n, l) && isMaxHeap(arr, n, r); }