一、堆的本質與特性
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 1std::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); }