首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言實現最小堆與優先隊列:從原理到工業級代碼完整指南

     編輯:關於C語言

堆與優先隊列核心概念

  • 堆:完全二叉樹,滿足堆序性。最小堆:父節點 ≤ 子節點;根節點為全局最小值。
  • 優先隊列:元素帶優先級,出隊時優先級最高(最小堆中數值最小)者先出。堆是實現優先隊列的最優數據結構,插入/刪除均為 O(log n)。
  • 數組存儲:索引 1 為根,左子 2i,右子 2i+1,父 i/2。索引 0 可作哨兵簡化邊界判斷。

核心操作圖解

插入:上濾

     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   ?

將最後元素移至根,與兩子節點中較小者比較,若更大則交換,直到滿足堆序或成為葉節點。

完整工業級實現 (MinHeap.h / MinHeap.c)

頭文件 MinHeap.h

#ifndef MIN_HEAP_H
#define MIN_HEAP_H

#include 

typedef 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

核心實現 MinHeap.c (關鍵片段)

#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;
}

常見坑與優化建議

  1. 哨兵值選擇:若元素類型為無符號或指針,需確保哨兵小於所有合法值,或改用額外標志位。
  2. 擴容策略:動態增長時建議 capacity *= 2,均攤 O(1)。
  3. 索引 0 使用:哨兵法可省去 i/2 == 0 判斷,但需確保類型兼容。
  4. 線程安全:非線程安全,多線程需外部加鎖或使用無鎖堆。
  5. 刪除任意元素:標準堆不支持,需引入「索引堆」(Indexed Heap) 或配合哈希表定位位置,再 decreaseKey + deleteMin。

應用場景速查

  • 任務調度:最早截止時間優先 (EDF)、最短作業優先 (SJF)。
  • 圖算法:Dijkstra 最短路、Prim 最小生成樹、A* 启發式搜索。
  • 流式處理:Top-K 問題、中位數維護 (雙堆)、滑動窗口最大/最小值。
  • 事件驅動:定時器輪、離散事件模擬。
  • 壓縮編碼:霍夫曼樹構建。

總結

最小堆是優先隊列的標準實現,掌握上濾/下濾、哨兵技巧、Floyd O(n) 建堆、泛型化重構,即可應對絕大多數工程場景。代碼簡潔、性能優異、擴展性強,是系統程序員工具箱中的核心利器。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved