【附C源码】从零实现C语言堆数据结构:原理、实现与应用

【附C源码】从零实现C语言堆数据结构:原理、实现与应用

堆(Heap)作为优先级队列的经典实现,在任务调度、图算法(如Dijkstra、Prim)、Top-K问题等场景中有着广泛应用。本文将深入探讨堆的核心原理,并基于C语言标准库实现一个完整的、可用于生产环境的最小堆与最大堆。

一、堆的本质与性质

堆是一种特殊的完全二叉树,其核心特性在于父节点与子节点之间的序关系:

  • 最小堆:任意节点的值不大于其子节点的值,堆顶为全局最小值
  • 最大堆:任意节点的值不小于其子节点的值,堆顶为全局最大值

由于完全二叉树的结构特性,堆可以高效地映射到数组中存储,避免了指针带来的额外内存开销。

数组映射关系

对于索引为 iii 的节点:

  • 父节点索引:parent(i)=⌊(i−1)/2⌋parent(i) = \lfloor (i-1)/2 \rfloorparent(i)=⌊(i−1)/2⌋
  • 左子节点索引:left(i)=2i+1left(i) = 2i + 1left(i)=2i+1
  • 右子节点索引:right(i)=2i+2right(i) = 2i + 2right(i)=2i+2

数组存储
索引: 0, 1, 2, 3, 4, 5, 6
值: 10, 20, 15, 30, 25, 18, 22
堆的树形结构
10
20
15
30
25
18
22

二、核心操作的时间复杂度分析

操作 时间复杂度 说明
插入元素 O(log⁡n)O(\log n)O(logn) 从叶节点向上调整
删除堆顶 O(log⁡n)O(\log n)O(logn) 将尾元素移至堆顶后向下调整
查看堆顶 O(1)O(1)O(1) 直接访问数组首元素
Floyd建堆 O(n)O(n)O(n) 自底向上批量建堆,优于逐元素插入

Floyd建堆的线性复杂度是一个常被忽视但极为重要的优化点。对于 nnn 个元素的建堆过程,时间复杂度可推导为:

T(n)=∑h=0⌊log⁡n⌋⌈n2h+1⌉⋅O(h)=O(n) T(n) = \sum_{h=0}^{\lfloor \log n \rfloor} \lceil \frac{n}{2^{h+1}} \rceil \cdot O(h) = O(n) T(n)=h=0∑⌊logn⌋⌈2h+1n⌉⋅O(h)=O(n)

三、数据结构定义

c 复制代码
typedef enum {
    MIN_HEAP,   // 父节点 <= 子节点
    MAX_HEAP    // 父节点 >= 子节点
} HeapType;

typedef struct {
    int* data;          // 动态数组
    int size;           // 当前元素数
    int capacity;       // 数组容量
    HeapType type;      // 堆类型标识
} Heap;

采用函数指针或宏定义实现比较逻辑的抽象,可以在编译期确定堆类型,避免运行时的分支判断开销。

四、关键算法实现

4.1 向上调整(Heapify Up)

插入操作将新元素置于数组末尾,随后沿父节点方向逐级比较、交换,直至满足堆性质。


新元素插入末尾
当前节点优先级高于父节点?
交换两节点
索引移至父节点
调整完成

c 复制代码
static void heapifyUp(Heap* h, int index) {
    while (index > 0) {
        int parent = PARENT(index);
        if (hasHigherPriority(h, h->data[index], h->data[parent])) {
            SWAP(h->data[index], h->data[parent]);
            index = parent;
        } else {
            break;
        }
    }
}

4.2 向下调整(Heapify Down)

删除堆顶时,将末尾元素移至堆顶后,从根节点开始与子节点比较,与优先级较高的子节点交换,直至叶子节点。

c 复制代码
static void heapifyDown(Heap* h, int index) {
    while (true) {
        int left = LEFT(index);
        int right = RIGHT(index);
        int priority = index;
        
        if (left < h->size && hasHigherPriority(h, h->data[left], h->data[priority]))
            priority = left;
        if (right < h->size && hasHigherPriority(h, h->data[right], h->data[priority]))
            priority = right;
        
        if (priority == index) break;
        
        SWAP(h->data[index], h->data[priority]);
        index = priority;
    }
}

4.3 Floyd建堆算法

与逐个插入的 O(nlog⁡n)O(n \log n)O(nlogn) 复杂度相比,Floyd算法通过自底向上的批量调整,将建堆复杂度优化至线性。

c 复制代码
Heap* heapBuild(int* arr, int n, HeapType type) {
    Heap* h = heapCreate(n > 4 ? n : 4, type);
    memcpy(h->data, arr, sizeof(int) * n);
    h->size = n;
    
    // 从最后一个非叶子节点开始向下调整
    for (int i = PARENT(n - 1); i >= 0; i--) {
        heapifyDown(h, i);
    }
    return h;
}

五、堆排序的实现

堆排序是一种原地、不稳定排序算法,核心思想是:

  1. 将待排序数组构建为最大堆(升序)或最小堆(降序)
  2. 反复将堆顶元素与末尾元素交换,堆规模减一
  3. 对新的堆顶执行向下调整
  4. 重复步骤2-3直至堆为空
c 复制代码
void heapSort(int* arr, int n, bool ascending) {
    HeapType type = ascending ? MAX_HEAP : MIN_HEAP;
    Heap* h = heapBuild(arr, n, type);
    
    for (int i = n - 1; i >= 0; i--) {
        heapPop(h, &arr[i]);
    }
    heapDestroy(h);
}

堆排序的时间复杂度恒为 O(nlog⁡n)O(n \log n)O(nlogn),空间复杂度为 O(1)O(1)O(1)(若采用原地建堆优化)。

六、工程实践要点

6.1 动态扩容策略

当容量不足时,采用倍增策略扩容,均摊插入复杂度仍为 O(1)O(1)O(1)。

c 复制代码
static bool heapExpand(Heap* h) {
    int newCapacity = h->capacity * 2;
    int* newData = realloc(h->data, sizeof(int) * newCapacity);
    if (!newData) return false;
    h->data = newData;
    h->capacity = newCapacity;
    return true;
}

6.2 删除任意位置元素

删除非堆顶元素时,用末尾元素填充被删位置,随后同时执行向上和向下调整,确保堆性质恢复。

c 复制代码
bool heapRemoveAt(Heap* h, int index, int* outValue) {
    *outValue = h->data[index];
    h->data[index] = h->data[--h->size];
    
    // 双方向调整,仅需其中一个会生效
    heapifyUp(h, index);
    heapifyDown(h, index);
    return true;
}

七、应用场景与扩展

7.1 优先级队列

堆是实现优先级队列的理想数据结构,支持高效地获取最高优先级元素。

7.2 Top-K问题

维护一个大小为K的最小堆,遍历数据流时,当新元素大于堆顶则替换,最终堆中即为最大的K个元素。

7.3 图算法优化

Dijkstra最短路径算法和Prim最小生成树算法中,使用堆优化可将时间复杂度从 O(V2)O(V^2)O(V2) 降至 O((V+E)log⁡V)O((V+E)\log V)O((V+E)logV)。

八、总结

本文实现的堆数据结构具备以下特点:

  • 支持最小堆与最大堆双模式
  • 采用动态数组实现,自动扩容
  • Floyd建堆算法保证线性建堆复杂度
  • 完整的堆排序功能
  • 零第三方依赖,仅使用C标准库

完整代码已开源,可作为学习数据结构的参考实现,也可根据实际需求扩展为泛型版本(通过void*与比较函数指针)。


⚠️源码地址:https://github.com/anjuxi/C-heap

相关推荐
gaosushexiangji4 小时前
DIC系统推荐:基于千眼狼三维数字图像相关的无人机旋翼疲劳试验全场应变与位移测量
人工智能·算法
小王C语言6 小时前
【线程概念与控制】:线程封装
jvm·c++·算法
kyle~6 小时前
工程数学---点云配准卡布施(Kabsch)算法(求解最优旋转矩阵)
线性代数·算法·矩阵
三品吉他手会点灯6 小时前
C语言学习笔记 - 35.数据类型 - printf函数的非输出控制符与格式优化
c语言·开发语言·笔记·学习
张二娃同学6 小时前
03_变量常量与输入输出_printf与scanf详解
算法
江南十四行7 小时前
并发编程(一)
java·jvm·算法
Ghost Face...7 小时前
U-Boot SPL阶段与主阶段深度解析:从ROM到Kernel的完整引导之旅(ARMv8)
c语言
z200509307 小时前
今日算法(依旧二叉树)
算法·leetcode·职场和发展
Zxc_8 小时前
《遗传算法:从自然选择到Rastrigin函数优化,手写一个完整的进化求解器》
算法