C语言实现堆(优先队列)详解

#新星杯·14天创作挑战营·第10期#

一、堆与优先队列简介

堆(Heap)是一种特殊的完全二叉树结构,满足任意节点的值都大于等于(或小于等于)其子节点的值。本文实现的是最大堆(大根堆)。优先队列是堆的典型应用,支持以下两种核心操作:

  • 插入元素:时间复杂度O(logn)

  • 弹出最大值:时间复杂度O(logn)

源代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define cmp >
#define ROOT 1
#define FATHER(i) ((i) / 2)
#define LEFT(i)   ((i) * 2)
#define RIGHT(i)  ((i) * 2 + 1)
#define swap(a, b) { \
    printf("swap(%d, %d)\n", a, b); \
    int __c = (a); \
    (a) = (b); \
    (b) = __c; \
}

typedef struct PriorityQueue {
    int* __data, * data;
    int size, n;
} PriorityQueue;

PriorityQueue* initPQ(int size) {
    PriorityQueue* p = (PriorityQueue*)malloc(sizeof(PriorityQueue));
    p->__data = (int*)malloc(sizeof(int) * size);
    p->data = p->__data - ROOT;
    p->size = size;
    p->n = 0;
    return p;
}

int empty(PriorityQueue* p) {
    return p->n == 0;
}
int full(PriorityQueue* p) {
    return p->n == p->size;
}
int top(PriorityQueue* p) {
    return p->data[ROOT];
}

void up_update(int* data, int i) {
    printf("\nUP update : %d\n", data[i]);
    while (i > ROOT && data[i] cmp data[FATHER(i)]) {
        swap(data[i], data[FATHER(i)]);
        i = FATHER(i);
    }
    printf("\n");
    return;
}

void down_update(int* data, int i, int n) {
    printf("\ndown update : %d\n", data[i]);
    while (LEFT(i) <= n) {
        int ind = i, l = LEFT(i), r = RIGHT(i);
        if (data[l] cmp data[ind]) ind = l;
        if (r <= n && data[r] cmp data[ind]) ind = r;
        if (ind == i) break;
        swap(data[i], data[ind]);
        i = ind;
    }
    printf("\n");
    return;
}

int push(PriorityQueue* p, int x) {
    if (full(p)) return 0;
    p->n += 1;
    p->data[p->n] = x;
    up_update(p->data, p->n);
    return 1;
}

int pop(PriorityQueue* p) {
    if (empty(p)) return 0;
    p->data[ROOT] = p->data[p->n];
    p->n -= 1;
    down_update(p->data, ROOT, p->n);
    return 1;
}

void clearPQ(PriorityQueue* p) {
    if (p == NULL) return;
    free(p->__data);
    free(p);
    return;
}

void output(PriorityQueue* p) {
    printf("PQ(%d) : ", p->n);
    for (int i = 1; i <= p->n; i++) {
        printf("%d ", p->data[i]);
    }
    printf("\n");
    return;
}

int main() {
    int op, x;
#define MAX_OP 100
    PriorityQueue* p = initPQ(MAX_OP);
    while (~scanf("%d", &op)) {
        if (op == 1) {
            scanf("%d", &x);
            printf("insert %d to priority_queue : \n", x);
            push(p, x); // push
            output(p);
        }
        else {
            printf("pop : %d\n", top(p));
            pop(p);     // pop
            output(p);
        }
    }
    clearPQ(p);
    return 0;
}

二、核心代码结构解析

2.1 数据结构定义

c

typedef struct PriorityQueue {
    int* __data;    // 数据存储空间
    int* data;       // 指向__data[1]的指针
    int size;        // 最大容量
    int n;           // 当前元素个数
} PriorityQueue;

使用动态数组实现堆,data指针指向__data[1]的位置,使得数组下标从1开始计算,便于父子节点访问。

2.2 关键宏定义

c

#define cmp >        // 定义比较符号实现最大堆
#define ROOT 1       // 根节点下标
#define FATHER(i) ((i)/2)
#define LEFT(i)  ((i)*2)
#define RIGHT(i) ((i)*2+1)

通过修改cmp宏可以快速实现最小堆。三个宏定义简化了父子节点的位置计算。

2.3 堆的初始化

c

PriorityQueue* initPQ(int size) {
    PriorityQueue* p = (PriorityQueue*)malloc(sizeof(PriorityQueue));
    p->__data = (int*)malloc(sizeof(int)*size);
    p->data = p->__data - ROOT; // 调整指针偏移
    p->size = size;
    p->n = 0;
    return p;
}

data指针指向__data[1]的巧妙设计,使得所有元素从下标1开始存储。

三、核心操作实现

3.1 插入元素(上浮调整)

c

void up_update(int* data, int i) {
    while(i > ROOT && data[i] cmp data[FATHER(i)]) {
        swap(data[i], data[FATHER(i)]);
        i = FATHER(i);
    }
}

int push(PriorityQueue* p, int x) {
    if(full(p)) return 0;
    p->data[++p->n] = x; // 插入到末尾
    up_update(p->data, p->n);
    return 1;
}

插入流程:

  1. 将新元素放入数组末尾

  2. 自底向上与父节点比较

  3. 若大于父节点则交换位置

  4. 重复直到满足堆性质

3.2 弹出元素(下沉调整)

c

void down_update(int* data, int i, int n) {
    while(LEFT(i) <= n) {
        int ind = i, l = LEFT(i), r = RIGHT(i);
        if(data[l] cmp data[ind]) ind = l;
        if(r <= n && data[r] cmp data[ind]) ind = r;
        if(ind == i) break;
        swap(data[i], data[ind]);
        i = ind;
    }
}

int pop(PriorityQueue* p) {
    if(empty(p)) return 0;
    p->data[ROOT] = p->data[p->n--]; // 末尾元素覆盖根节点
    down_update(p->data, ROOT, p->n);
    return 1;
}

删除流程:

  1. 用最后一个元素覆盖根节点

  2. 自顶向下与子节点比较

  3. 与较大的子节点交换位置

  4. 重复直到满足堆性质

四、使用示例

c

int main() {
    PriorityQueue* p = initPQ(10);
    
    // 插入元素测试
    push(p, 5); // [5]
    push(p, 3); // [5,3]
    push(p, 8); // [8,3,5]
    
    // 弹出测试
    while(!empty(p)) {
        printf("Current max: %d\n", top(p));
        pop(p);
    }
    
    clearPQ(p);
    return 0;
}

输出结果:

Current max: 8
Current max: 5
Current max: 3

五、完整代码说明

完整代码实现了以下功能:

  1. 堆的初始化与销毁

  2. 元素插入与上浮调整

  3. 最大值弹出与下沉调整

  4. 堆空/满状态判断

  5. 调试打印交换过程

通过swap宏中的printf可以观察元素调整过程,实际使用时可移除调试输出。

六、应用场景

优先队列常用于:

  • 任务调度系统

  • 实时计算最大值场景

  • 图算法(Dijkstra算法等)

  • 合并K个有序链表

该实现的时间复杂度:

  • 插入操作:O(logn)

  • 删除操作:O(logn)

  • 获取最大值:O(1)

注:代码出自B站海贼胡船长

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

维维宝宝最可爱啦QWQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值