在排序算法中,堆排序利用了完全二叉树形式的堆结构,结合了插入排序与合并排序的优点,能够以 O ( n log n ) O(n\log{n}) O(nlogn)的时间完成排序。优先级队列是可基于堆结构进行实现的一种数据结构,在计算机系统中可以用来记录不同作业的相对优先级关系,从而进行作业调度。本文将介绍什么是优先级队列,以及使用优先级队列实现栈和队列的C语言算法。
1. 什么是优先级队列
优先级队列(Priority Queue)是一个具有有限元素的线性结构,其中每个元素都有一个数值key标识其优先级。优先级队列又可分为最大优先级队列和最小优先级队列。
1.1 最大优先级队列
一个最大优先级队列支持以下操作:
INSERT(S, x)
:把元素x插入集合S中;MAXIMUM(S)
:返回集合S中具有最大优先级的元素;EXTRACT-MAX(S)
:去掉并返回S中具有最大优先级的元素;INCREASE-KEY(S, x, k)
:将元素x的优先级增加到k,这里假设k值不能小于x的原始关键字的值。
1.2 最小优先级队列
一个最小优先级队列支持以下操作:
INSERT(S, x)
:把元素x插入集合S中;MINIMUM(S)
:返回集合S中具有最小优先级的元素;EXTRACT-MIN(S)
:去掉并返回S中具有最小优先级的元素;DECREASE-KEY(S, x, k)
:将元素x的优先级减小到k,这里假设k值不能大于x的原始关键字的值。
可以发现最大优先级队列与最小优先级队列的区别仅在于后三个操作针对的元素优先级不同。
2. 设计思路
在具体底层实现上,最大优先级队列可用大顶堆实现,最小优先级队列可用小顶堆实现。以大顶堆为例,它支持以下操作:
MAX-HEAPIFY
:运行时间为 O ( log n ) O(\log{n}) O(logn),保持最大堆性质;BUILD-MAX-HEAP
:以线性时间运行,将无序的输入数组转换为最大堆;MAX-HEAP-INSERT
,HEAP-EXTRACT-MAX
,HEAP-INCREASE-KEY
和HEAP-MAXIMUM
过程的运算时间为 O ( log n ) O(\log{n}) O(logn) , 可以让堆结构作为优先队列使用。
小顶堆支持操作类似。在使用最大优先级队列实现栈和队列过程中,我将元素入栈(或队)的序号作为元素的优先级。在使用最小优先级队列实现队列的过程中,我将元素入队的序号的相反数作为元素的优先级。具体代码可见第3节。
3. 程序代码
以下C语言程序代码分别实现了:
- 最大优先队列实现后进先出栈(进栈元素优先级递增),见stack.cpp;
- 最小优先队列实现先进先出队列(进队元素优先级递增),见queue_min_priority.cpp;
- 最大优先队列实现先进先出队列(进队元素优先级递减),见queue_max_priority.cpp。
3.1 最大优先队列实现栈
stack.cpp代码如下,在stack.cpp中首先构建最大堆,实现最大优先队列,然后利用最大优先队列实现栈。
#include <stdio.h>
#include <stdlib.h>#define MIN -1000000 //定义最小的优先级 //最大优先级队列实现后进先出栈:1.利用最大堆实现最大优先队列,2.利用最大优先队列实现栈,进栈的元素优先级递增
typedef struct Node {int elem; //储存栈中的元素值 int key; //为每一个进栈的元素赋一个优先级值
} Node;typedef struct Stack {int length; //栈的长度 Node *data; //栈中的元素
} Stack;/*************************************Part1.利用最大堆实现最大优先队列*************************************/
void swap(Node *a, Node *b) { //交换元素a与元素b Node tmp;tmp.elem = (*a).elem;tmp.key = (*a).key;(*a).elem = (*b).elem;(*a).key = (*b).key;(*b).elem = tmp.elem;(*b).key = tmp.key;
}void max_heapify(Node *a, int size, int i) { //使元素的优先级满足最大堆的性质 int left = 2 * i; //i的左孩子 int right = 2 * i + 1; //i的右孩子 int largest; //选出具有最大优先级的元素,下标存在largest中 if (left <= size && a[left].key > a[i].key) {largest = left;}else {largest = i;} if (right <= size && a[right].key > a[largest].key) {largest = right;}if (largest != i) { //具有最大优先级的元素是i的某个孩子结点 swap(&a[i], &a[largest]);max_heapify(a, size, largest); //递归调用 }
}void build_max_heap(Node *a, int size) { //构建最大堆 int i;for(i = size / 2; i >= 1; i--) {max_heapify(a, size, i);}
}Node heap_maximum(Node *a) { //返回最大优先级队列中优先级最大的元素,即最大堆的顶 return a[1];
}void heap_increase_key(Node *a, int i, int key) { //将元素a[i]的优先级增加到 key if (key < a[i].key) {printf("New key is smaller than current key!(Error from heap_increase_key)\n");exit(0);}a[i].key = key;while (i > 1 && a[i / 2].key < a[i].key) {swap(&a[i], &a[i / 2]);i = i / 2;}
}Node heap_extract_max(Node *a, int *size) { //去掉并返回最大优先队列中具有最大优先级的元素 if (*size < 1) {printf("Heap underflow!(Error from heap_extract_max)\n");exit(-1);}Node max = a[1];a[1] = a[*size];(*size)--;max_heapify(a, *size, 1);return max;
}void max_heap_insert(Node *a, int *size, int key, int elem) { //向最大优先队列中插入元素,即扩展最大堆 (*size)++;a[*size].key = MIN;a[*size].elem = elem;heap_increase_key(a, *size, key);
}
/************************************************Part1 ends************************************************//*************************************Part2.利用最大优先队列实现栈操作*************************************/
bool is_stack_empty(Stack *s) { //判断栈是否为空 if ((*s).length == 0) return true;return false;
}void stack_push(Stack *s, int elem) { //入栈 Node *old = (*s).data;(*s).data = (Node *)malloc(((*s).length + 2) * sizeof(Node)); //开创新的空间 int i;for (i = 1; i <= (*s).length; i++) {(*s).data[i] = old[i];}free(old); //释放旧的空间 max_heap_insert(s->data, &s->length, s->length + 1, elem);
}int stack_getTop(Stack *s) { //获取栈顶元素 return heap_maximum(s->data).elem;
}int stack_pop(Stack *s) { //出栈 return heap_extract_max(s->data, &s->length).elem;
}int main() {Stack s;printf("Input the length of the stack:\n");scanf("%d", &s.length);s.data = (Node *)malloc((s.length + 1) * sizeof(Node));printf("Input the element:\n");int i;for(i = 1; i <= s.length; i++){s.data[i].key = i;scanf("%d", &s.data[i].elem);}printf("Build the heap for the stack!\n");build_max_heap(s.data, s.length);printf("The result of the heap:\n");for(i = 1; i <= s.length; i++)printf("%d(Priority:%d) ", s.data[i].elem, s.data[i].key);printf("\n");int push_elem;printf("Input the element you want to push into the stack:\n");scanf("%d", &push_elem);stack_push(&s, push_elem);printf("Now the top of the stack is: %d\n", stack_getTop(&s));printf("Output the stack:\n");while (!is_stack_empty(&s)) {printf("pop: %d\n", stack_pop(&s));}return 0;
}
3.2 最小优先队列实现队列
在queue_min_priority.cpp中首先构建最小堆,实现最小优先队列,然后利用最小优先队列实现队列,代码如下:
#include <stdio.h>
#include <stdlib.h>#define MAX 1000000 //定义最大的优先级 //最小优先级队列实现先进先出队列:1.利用最小堆实现最小优先队列,2.利用最小优先队列实现队列,进队的元素优先级递增
typedef struct Node {int elem; //储存队列中的元素值 int key; //为每一个进队的元素赋一个优先级值
} Node;typedef struct Queue {int length; //队列的长度 Node *data; //队列中的元素
} Queue;/*************************************Part1.利用最小堆实现最小优先队列*************************************/
void swap(Node *a, Node *b) { //交换元素a与元素b Node tmp;tmp.elem = (*a).elem;tmp.key = (*a).key;(*a).elem = (*b).elem;(*a).key = (*b).key;(*b).elem = tmp.elem;(*b).key = tmp.key;
}void min_heapify(Node *a, int size, int i) { //使元素的优先级满足最小堆的性质 int left = 2 * i; //i的左孩子 int right = 2 * i + 1; //i的右孩子 int smallest; //选出具有最小优先级的元素,下标存在smallest中 if (left <= size && a[left].key < a[i].key) {smallest = left;}else {smallest = i;} if (right <= size && a[right].key < a[smallest].key) {smallest = right;}if (smallest != i) { //具有最小优先级的元素是i的某个孩子结点 swap(&a[i], &a[smallest]);min_heapify(a, size, smallest); //递归调用 }
}void build_min_heap(Node *a, int size) { //构建最小堆 int i;for(i = size / 2; i >= 1; i--) {min_heapify(a, size, i);}
}Node heap_minimum(Node *a) { //返回最小优先级队列中优先级最小的元素,即最小堆的顶 return a[1];
}void heap_decrease_key(Node *a, int i, int key) { //将元素a[i]的优先级减少到 key if (key > a[i].key) {printf("New key is larger than current key!(Error from heap_decrease_key)\n");exit(0);}a[i].key = key;while (i > 1 && a[i / 2].key > a[i].key) {swap(&a[i], &a[i / 2]);i = i / 2;}
}Node heap_extract_min(Node *a, int *size) { //去掉并返回最小优先队列中具有最小优先级的元素 if (*size < 1) {printf("Heap underflow!(Error from heap_extract_min)\n");exit(-1);}Node min = a[1];a[1] = a[*size];(*size)--;min_heapify(a, *size, 1);return min;
}void min_heap_insert(Node *a, int *size, int key, int elem) { //向最小优先队列中插入元素,即扩展最小堆 (*size)++;a[*size].key = MAX;a[*size].elem = elem;heap_decrease_key(a, *size, key);
}
/************************************************Part1 ends************************************************//************************************Part2.利用最小优先队列实现队列操作************************************/
bool is_queue_empty(Queue *q) { //判断队列是否为空 if ((*q).length == 0) return true;return false;
}void queue_push(Queue *q, int elem) { //入队 Node *old = (*q).data;(*q).data = (Node *)malloc(((*q).length + 2) * sizeof(Node)); //开创新的空间 int i;for (i = 1; i <= (*q).length; i++) {(*q).data[i] = old[i];}free(old); //释放旧的空间 min_heap_insert(q->data, &q->length, q->length + 1, elem);
}int queue_getHead(Queue *q) { //获取队首元素 return heap_minimum(q->data).elem;
}int queue_pop(Queue *q) { //出队 return heap_extract_min(q->data, &q->length).elem;
}int main() {Queue q;printf("Input the length of the queue:\n");scanf("%d", &q.length);q.data = (Node *)malloc((q.length + 1) * sizeof(Node));printf("Input the element:\n");int i;for(i = 1; i <= q.length; i++){q.data[i].key = i;scanf("%d", &q.data[i].elem);}printf("Build the heap for the Queue!\n");build_min_heap(q.data, q.length);printf("The result of the heap:\n");for(i = 1; i <= q.length; i++)printf("%d(Priority:%d) ", q.data[i].elem, q.data[i].key);printf("\n");int push_elem;printf("Input the element you want to push into the queue:\n");scanf("%d", &push_elem);queue_push(&q, push_elem);printf("Now the head of the queue is: %d\n", queue_getHead(&q));printf("Output the queue:\n");while (!is_queue_empty(&q)) {printf("pop: %d\n", queue_pop(&q));}return 0;
}
3.3 最大优先队列实现队列
queue_max_priority.cpp实现过程同stack.cpp,只是优先级不同,代码如下:
#include <stdio.h>
#include <stdlib.h>#define MIN -1000000 //定义最小的优先级 //最大优先级队列实现先进先出队列:1.利用最大堆实现最大优先队列,2.利用最大优先队列实现队列,进队的元素优先级递减
typedef struct Node {int elem; //储存队列中的元素值 int key; //为每一个进队的元素赋一个优先级值
} Node;typedef struct Queue {int length; //队列的长度 Node *data; //队列中的元素
} Queue;/*************************************Part1.利用最大堆实现最大优先队列*************************************/
void swap(Node *a, Node *b) { //交换元素a与元素b Node tmp;tmp.elem = (*a).elem;tmp.key = (*a).key;(*a).elem = (*b).elem;(*a).key = (*b).key;(*b).elem = tmp.elem;(*b).key = tmp.key;
}void max_heapify(Node *a, int size, int i) { //使元素的优先级满足最大堆的性质 int left = 2 * i; //i的左孩子 int right = 2 * i + 1; //i的右孩子 int largest; //选出具有最大优先级的元素,下标存在largest中 if (left <= size && a[left].key > a[i].key) {largest = left;}else {largest = i;} if (right <= size && a[right].key > a[largest].key) {largest = right;}if (largest != i) { //具有最大优先级的元素是i的某个孩子结点 swap(&a[i], &a[largest]);max_heapify(a, size, largest); //递归调用 }
}void build_max_heap(Node *a, int size) { //构建最大堆 int i;for(i = size / 2; i >= 1; i--) {max_heapify(a, size, i);}
}Node heap_maximum(Node *a) { //返回最大优先级队列中优先级最大的元素,即最大堆的顶 return a[1];
}void heap_increase_key(Node *a, int i, int key) { //将元素a[i]的优先级增加到 key if (key < a[i].key) {printf("New key is smaller than current key!(Error from heap_increase_key)\n");exit(0);}a[i].key = key;while (i > 1 && a[i / 2].key < a[i].key) {swap(&a[i], &a[i / 2]);i = i / 2;}
}Node heap_extract_max(Node *a, int *size) { //去掉并返回最大优先队列中具有最大优先级的元素 if (*size < 1) {printf("Heap underflow!(Error from heap_extract_max)\n");exit(-1);}Node max = a[1];a[1] = a[*size];(*size)--;max_heapify(a, *size, 1);return max;
}void max_heap_insert(Node *a, int *size, int key, int elem) { //向最大优先队列中插入元素,即扩展最大堆 (*size)++;a[*size].key = MIN;a[*size].elem = elem;heap_increase_key(a, *size, key);
}
/************************************************Part1 ends************************************************//************************************Part2.利用最大优先队列实现队列操作************************************/
bool is_queue_empty(Queue *q) { //判断队列是否为空 if ((*q).length == 0) return true;return false;
}void queue_push(Queue *q, int elem) { //入队 Node *old = (*q).data;(*q).data = (Node *)malloc(((*q).length + 2) * sizeof(Node)); //开创新的空间 int i;for (i = 1; i <= (*q).length; i++) {(*q).data[i] = old[i];}free(old); //释放旧的空间 max_heap_insert(q->data, &q->length, -1 * (q->length + 1), elem);
}int queue_getHead(Queue *q) { //获取队首元素 return heap_maximum(q->data).elem;
}int queue_pop(Queue *q) { //出队 return heap_extract_max(q->data, &q->length).elem;
}int main() {Queue q;printf("Input the length of the queue:\n");scanf("%d", &q.length);q.data = (Node *)malloc((q.length + 1) * sizeof(Node));printf("Input the element:\n");int i;for(i = 1; i <= q.length; i++){q.data[i].key = (-1) * i;scanf("%d", &q.data[i].elem);}printf("Build the heap for the Queue!\n");build_max_heap(q.data, q.length);printf("The result of the heap:\n");for(i = 1; i <= q.length; i++)printf("%d(Priority:%d) ", q.data[i].elem, q.data[i].key);printf("\n");int push_elem;printf("Input the element you want to push into the queue:\n");scanf("%d", &push_elem);queue_push(&q, push_elem);printf("Now the top of the queue is: %d\n", queue_getHead(&q));printf("Output the queue:\n");while (!is_queue_empty(&q)) {printf("pop: %d\n", queue_pop(&q));}return 0;
}
4. 运行结果
4.1 stack.cpp
- 首先用户需要输入stack的长度,然后为其输入栈中的元素。程序会给出栈中元素按优先级构造的最大堆结果。
- 之后程序会提示用户再次输入一个想要入栈的元素,按下回车,程序输出现在的栈顶元素,并将栈中的所有元素弹出。
4.2 queue_min_priority.cpp
- 首先用户需要输入队列的长度,然后为其输入队列中的元素。程序会给出队列中元素按优先级构造的最小堆结果。
- 之后程序会提示用户再次输入一个想要入队的元素,按下回车,程序输出现在的队首元素,并将队列中的所有元素弹出。
4.3 queue_max_priority.cpp
- 首先用户需要输入队列的长度,然后为其输入队列中的元素。程序会给出队列中元素按优先级构造的最大堆结果。
- 之后程序会提示用户再次输入一个想要入队的元素,按下回车,程序输出现在的队首元素,并将队列中的所有元素弹出。