一、堆与优先队列简介
堆(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; }
插入流程:
-
将新元素放入数组末尾
-
自底向上与父节点比较
-
若大于父节点则交换位置
-
重复直到满足堆性质
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; }
删除流程:
-
用最后一个元素覆盖根节点
-
自顶向下与子节点比较
-
与较大的子节点交换位置
-
重复直到满足堆性质
四、使用示例
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
五、完整代码说明
完整代码实现了以下功能:
-
堆的初始化与销毁
-
元素插入与上浮调整
-
最大值弹出与下沉调整
-
堆空/满状态判断
-
调试打印交换过程
通过swap
宏中的printf
可以观察元素调整过程,实际使用时可移除调试输出。
六、应用场景
优先队列常用于:
-
任务调度系统
-
实时计算最大值场景
-
图算法(Dijkstra算法等)
-
合并K个有序链表
该实现的时间复杂度:
-
插入操作:O(logn)
-
删除操作:O(logn)
-
获取最大值:O(1)