【C语言】【高级数据结构与算法】 优先队列
·
感谢阅读我的博客,请认真看下去。我会尝试以最简单明了的语言讲清楚该算法的本质。
定义
优先队列(Priority Queue)是一种抽象数据类型,类似于普通的队列或栈,但它不是遵循先进先出(FIFO)或后进先出(LIFO)的原则,而是根据元素的优先级进行排序。每次从优先队列中移除元素时,总是移除具有最高优先级的元素。优先级的定义可以根据实际应用场景的不同而不同,例如数字大小、字母顺序或其他任何可以比较的标准。
特性
- 插入:可以向优先队列中添加一个元素。新元素会被放置在合适的位置,以保持队列的有序状态。
- 删除最高优先级元素:从优先队列中移除并返回具有最高优先级的元素。这通常是指队列中的最小或最大元素,取决于队列是按照升序还是降序排列的。
- 获取最高优先级元素:查看队列中优先级最高的元素,但不移除它。
- 改变优先级:某些优先队列支持修改已存在元素的优先级,然后自动重新调整队列以保持正确的顺序。
应用场景
优先队列的应用非常广泛,包括但不限于:
- 任务调度:操作系统中使用优先队列来管理进程调度,确保高优先级的任务优先得到处理。
- 图形算法:如Dijkstra算法中使用优先队列来找到两个顶点之间的最短路径。
- 数据压缩:霍夫曼编码算法
- 资源分配:在网络路由、内存管理和磁盘调度等领域中用于优化资源分配。
- 最短路径算法:Dijkstra算法;
- 最小生成树算法:Prim算法;
- 事件驱动仿真:顾客排队算法;
- 排序问题:查找第 k 个最小元素。
总之,优先队列是一个非常有用的数据结构,尤其适用于那些需要根据元素的某些特征进行排序和选择的场景。
实现方式
优先队列可以通过多种方式实现,最常见的有以下几种:
- 数组(顺序存储)实现优先队列:
- 入队操作直接插入到数组队尾,时间复杂度为O(1)。出队操作需要遍历整个数组,找到优先级最高的元素,返回并删除该元素,时间复杂度为O(n)。
- 链表(链式存储)实现优先队列:
- 链表中的元素按照优先级排序,入队操作需要为待插入元素创建节点,并在链表中找到合适的插入位置, 时间复杂度为O(n)。出队操作直接返回链表队头元素,并删除队头元素,时间复杂度为O(1)。
- 二叉堆结构实现优先队列:
- 这是最常用的实现方式,因为它提供了很好的性能平衡。插入和删除操作的时间复杂度均为O(log n)。构建一个二叉堆结构,二叉堆按照优先级进行排序。入队操作就是将元素插入到二叉堆中合适位置,出队操作则返回二叉堆中优先级最大节点(根节点)并删除。之后重新调整二叉堆即可。
- 知识点补充:堆是一种特殊的完全二叉树。所有父结点都比子结点要小的完全二叉树我们称为最小堆。反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆。
- 斐波那契堆:
- 一种更加复杂的堆结构,提供更好的渐近性能,但在实际应用中由于常数因子较大,可能不如二叉堆表现好。
- 平衡二叉搜索树:
- 如红黑树或AVL树也可以用来实现优先队列,这些数据结构支持高效的插入、删除和查找操作。
- 跳表:
- 一种概率性的数据结构,能够提供平均O(log n)的插入和删除时间。
代码实现(使用堆实现 C语言)
第一步,定义优先队列的数据结构
定义优先队列的数据结构,包括数组、当前元素数量和数组的最大容量。并实现初始化及销毁方法。
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *array; // 存储元素的数组
int capacity; // 数组的最大容量
int curSize; // 当前元素数量
} PriorityQueue;
// 初始化优先队列
PriorityQueue* initPriorityQueue(int capacity) {
PriorityQueue *pq = (PriorityQueue*)malloc(sizeof(PriorityQueue));
pq->array = (int*)malloc(capacity * sizeof(int));
pq->capacity = capacity;
pq->curSize = 0;
return pq;
}
// 销毁优先队列
void destroyPriorityQueue(PriorityQueue *pq) {
free(pq->array);
free(pq);
}
第二步,实现优先队列的插入操作
插入一个新的元素到优先队列中末尾,然后调整并保持堆的性质。
void insert(PriorityQueue *pq, int value) {
// 判断优先队列是否已经满了,满了则直接return
if (pq->curSize == pq->capacity) {
printf("Priority queue is full\n");
return;
}
// 将元素插入优先队列末尾,需要注意此时会破坏最小堆的性质。
int index = pq->curSize;
pq->array[index] = value;
pq->curSize++;
// 调整堆,保持最小堆性质
while (index > 0 && (pq->array[(index - 1) / 2] > pq->array[index])) {
int temp = pq->array[(index - 1) / 2];
pq->array[(index - 1) / 2] = pq->array[index];
pq->array[index] = temp;
index = (index - 1) / 2;
}
}
代码解释(写给小白看):
-
while循环的代码逻辑:
- 条件index > 0:确保当前节点不是根节点(根节点没有父节点)。
- 条件(pq->array[(index - 1) / 2] > pq->array[index]): 其中pq->array[(index - 1) / 2]是pq->array[index]的父节点。在此while循环中的目的是确保当前节点的值小于其父节点的值。如果当前节点的值小于其父节点的值,则需要交换它们的位置,以保持最小堆的性质。
- 交换节点并更新索引:
- int temp = pq->array[(index - 1) / 2];:保存父节点的值到temp。
- pq->array[(index - 1) / 2] = pq->array[index];:将当前节点的值赋给父节点。
- pq->array[index] = temp;:将原来父节点的值赋给当前节点。
- index = (index - 1) / 2;:将当前节点的索引更新为其父节点的索引。然后进入下一轮循环向上检查并继续调整堆直到调整完毕。
-
常见问题:为什么pq->array[(index - 1) / 2]是pq->array[index]的父节点?
- 答曰:二叉堆通常用一个数组来表示,其中每个节点的索引与其子节点和父节点的索引之间有明确的关系。
- 假设数组的索引是从0开始的,那么对于一个索引为 index 的节点有如下性质:
- 其左子节点的索引为 2 * index + 1
- 其右子节点的索引为 2 * index + 2
- 其父亲节点的索引为 (index - 1) / 2
第三步,实现优先队列的删除操作
删除优先队列中的最小元素,然后调整并保持堆的性质。最后返回最小值。
int deleteMin(PriorityQueue *pq) {
// 判断当前优先队列是否为空,空则无法删除,直接return
if (pq->curSize == 0) {
printf("Priority queue is empty\n");
return -1;
}
// 删除最小元素,注意此时会破坏最小二叉堆的性质。
int min = pq->array[0];
// 将最后一个元素放到堆的顶端,从而保证pq仍然是一个完全二叉树,不至于直接被一劈两半。
pq->array[0] = pq->array[pq->curSize - 1];
pq->curSize--;
// 调整并保持堆的性质
int index = 0;
while (index < pq->curSize) {
int leftChild = 2 * index + 1; // 左儿子
int rightChild = 2 * index + 2; // 右儿子
int smallest = index;
// 找到最小元素的索引
if ((leftChild < pq->curSize) && (pq->array[leftChild] < pq->array[smallest])) {
smallest = leftChild;
}
if (rightChild < pq->curSize && pq->array[rightChild] < pq->array[smallest]) {
smallest = rightChild;
}
// 将找到的最小元素与当前元素交换。
if (smallest != index) {
int temp = pq->array[smallest];
pq->array[smallest] = pq->array[index];
pq->array[index] = temp;
index = smallest;
} else {
break; // 说明当前节点已经是其子节点中的最小值节点,不需要再进行调整
}
}
return min;
}
最后使用main函数调用
int main() {
// 初始化一个最大容量为10的优先队列。
PriorityQueue *pq = initPriorityQueue(10);
insert(pq, 5);
insert(pq, 3);
insert(pq, 8);
insert(pq, 1);
insert(pq, 2);
printf("当前优先队列为:");
for (int i = 0;i< pq->curSize;i++) {
printf("%d ",pq->array[i]);
}
printf("\n最小元素为: %d\n", deleteMin(pq)); // 输出 1
printf("当前优先队列为:");
for (int i = 0;i< pq->curSize;i++) {
printf("%d ",pq->array[i]);
}
printf("\n最小元素为: %d\n", deleteMin(pq)); // 输出 2
printf("当前优先队列为:");
for (int i = 0;i< pq->curSize;i++) {
printf("%d ",pq->array[i]);
}
printf("\n");
destroyPriorityQueue(pq);
return 0;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)