5 插入 2 2
/ \ --> / \ --> / \
8 9 8 9 2 5 9
/ / /
10 10 8
/ / \
2 10 2 (繼續上濾至根)
新元素置於末尾,與父節點比較,若更小則交換,直到滿足堆序或到達根。
2 刪除 2 (根) 10 下濾 5
/ \ --> 移末尾 10 到根 --> / \ --> 與較小子 5 交換 / \
5 9 10 9 5 9 10 9
/ \ / \ / \ / \
8 10 8 ? 8 ? 8 ?
將最後元素移至根,與兩子節點中較小者比較,若更大則交換,直到滿足堆序或成為葉節點。
#ifndef MIN_HEAP_H #define MIN_HEAP_H #includetypedef int ElementType; // 可改為 void* 實現泛型 typedef struct MinHeap *MinPriorityQueue; /* 基礎操作 */ MinPriorityQueue initialize(int maxElements); // 創建,指定容量上限 void destroy(MinPriorityQueue pq); // 銷毀並釋放內存 void makeEmpty(MinPriorityQueue pq); // 清空元素,保留容量 void insert(ElementType x, MinPriorityQueue pq); // 插入 O(log n) ElementType deleteMin(MinPriorityQueue pq); // 刪除並返回最小值 O(log n) ElementType findMin(MinPriorityQueue pq); // 查看最小值 O(1) int isEmpty(MinPriorityQueue pq); int isFull(MinPriorityQueue pq); /* 批量建堆 (O(n)) */ MinPriorityQueue buildHeap_insert(ElementType *arr, int n); // 逐個插入 (平均 O(n)) MinPriorityQueue buildHeap_percolate(ElementType *arr, int n); // Floyd 下濾建堆 (嚴格 O(n)) /* 調試 */ void printMinPriorityQueue(MinPriorityQueue pq); #endif
#include "MinHeap.h" #include#include #define SENTINEL -1 // 哨兵,必須小於所有合法元素 struct MinHeap { int capacity; // 最大容量 int size; // 當前元素數 ElementType *elements; // 索引 1..size 存數據,0 存哨兵 }; /* 初始化:分配結構體 + 元素數組 (capacity+1) */ MinPriorityQueue initialize(int maxElements) { if (maxElements <= 0) return NULL; MinPriorityQueue pq = malloc(sizeof(struct MinHeap)); if (!pq) return NULL; pq->elements = malloc(sizeof(ElementType) * (maxElements + 1)); if (!pq->elements) { free(pq); return NULL; } pq->capacity = maxElements; pq->size = 0; pq->elements[0] = SENTINEL; return pq; } /* 插入:上濾 */ void insert(ElementType x, MinPriorityQueue pq) { if (!pq || isFull(pq)) return; int i = ++pq->size; for (; x < pq->elements[i/2]; i /= 2) pq->elements[i] = pq->elements[i/2]; pq->elements[i] = x; } /* 刪除最小:下濾 */ ElementType deleteMin(MinPriorityQueue pq) { if (!pq || isEmpty(pq)) return SENTINEL; ElementType min = pq->elements[1]; ElementType last = pq->elements[pq->size--]; int i, child; for (i = 1; i*2 <= pq->size; i = child) { child = i*2; if (child != pq->size && pq->elements[child+1] < pq->elements[child]) child++; // 取較小子節點 if (last <= pq->elements[child]) break; pq->elements[i] = pq->elements[child]; } pq->elements[i] = last; return min; } /* Floyd 建堆:從最後一個非葉節點反向下濾 O(n) */ static void percolateDown(ElementType *arr, int n, int i) { int child; ElementType tmp = arr[i]; for (; i*2 <= n; i = child) { child = i*2; if (child != n && arr[child+1] < arr[child]) child++; if (tmp <= arr[child]) break; arr[i] = arr[child]; } arr[i] = tmp; } MinPriorityQueue buildHeap_percolate(ElementType *arr, int n) { if (!arr || n <= 0) return NULL; MinPriorityQueue pq = malloc(sizeof(struct MinHeap)); if (!pq) return NULL; pq->elements = malloc(sizeof(ElementType) * (n + 1)); if (!pq->elements) { free(pq); return NULL; } pq->elements[0] = SENTINEL; for (int i = 1; i <= n; ++i) pq->elements[i] = arr[i-1]; for (int i = n/2; i >= 1; --i) percolateDown(pq->elements, n, i); pq->size = n; pq->capacity = n * 2; // 預留擴容空間 return pq; }
| 方法 | 時間複雜度 | 適用場景 | 特點 |
|---|---|---|---|
逐個插入 buildHeap_insert | 平均 O(n),最壞 O(n log n) | 數據流式到達、在線構建 | 代碼簡單,利用現有 insert |
Floyd 下濾 buildHeap_percolate | 嚴格 O(n) | 已有完整數組、離線批量構建 | 理論最優,常數因子小 |
測試數據 {5,4,3,2,1} 輸出驗證:
// insert 建堆 1, 2, 4, 5, 3 // percolate 建堆 1, 5, 3, 2, 4 // 兩者皆滿足最小堆性質,結構不同但均正確
typedef int (*CompareFunc)(const void *, const void *);
struct GenericHeap {
int capacity, size;
void **elements; // 指針數組
CompareFunc cmp; // 比較函數
void (*free_elem)(void *); // 可選:元素銷毀器
};
/* 比較函數示例:整數升序 */
int cmp_int_asc(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
/* 插入邏輯調整 */
void generic_insert(void *x, struct GenericHeap *h) {
int i = ++h->size;
for (; i > 1 && h->cmp(x, h->elements[i/2]) < 0; i /= 2)
h->elements[i] = h->elements[i/2];
h->elements[i] = x;
}
capacity *= 2,均攤 O(1)。i/2 == 0 判斷,但需確保類型兼容。decreaseKey + deleteMin。最小堆是優先隊列的標準實現,掌握上濾/下濾、哨兵技巧、Floyd O(n) 建堆、泛型化重構,即可應對絕大多數工程場景。代碼簡潔、性能優異、擴展性強,是系統程序員工具箱中的核心利器。