分治算法四:基于二叉堆的优先队列
一、背景介绍
1、优先队列:队列中的元素被指定优先级,元素按优先级出队。针对二叉堆,根节点就为最值。队列有两个基本操作:入队和出队。
2、入队操作分析:
将待入队的元素放在堆的末尾,判断该元素与与其父节点的优先级,确定是否需要进行交换;若需要交换,则在交换后继续进行判断。
3、出队操作分析:
将队首元素取出,将队尾元素覆盖当前队首元素(队列长度减一),并维护剩余元素组成堆的性质。
4、数据类型:数据集合+各种操作。
二、代码实现
二叉堆的性质维护heapify函数分析,请参考分治算法四:二叉堆的创建
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/************************************堆性质维护********************************************/
// 比较函数
int intGreater(void *x, void *y)
{
return *(int *)x - *(int *)y;
}
int intLess(void *x, void *y)
{
return *(int *)y - *(int *)x;
}
// 交换两个变量的值
void swap(void *x, void *y, int size)
{
void *temp = (void*)malloc(size);
memcpy(temp, x, size);
memcpy(x, y, size);
memcpy(y, temp,size);
free(temp);
}
// 根据子节点i,获取父节点索引
int parent(int i)
{
return (i - 1) / 2;
}
// 在左右子树满足堆特性的前提下(最简单情形为左右子树为单个元素),判断父节点加入后,是否满足堆特性并进行调整
void heapify(void *a, int size, int parent, int heapSize, int(*comp)(void *, void *))
{
// 根据父节点索引i,得到左叶子节点和右叶子节点的索引,根节点的索引从0开始
int left = 2 * parent + 1;
int right = 2 * parent + 2;
int most;
// 比较左叶子节点和父节点大小,most = max(left, parent)
if (left < heapSize && comp(a + left * size, a + parent * size) > 0) {
most = left;
} else {
most = parent;
}
// 比较右叶子节点和most节点, most = max(right, most)
if (right < heapSize && comp(a + right * size, a + most * size) > 0) {
most = right;
}
// 此时most = max(parent, left, right); 若不满足堆特性,将most与父节点进行交换,并对交换后的叶子节点继续判断
if (most != parent) {
swap(a + parent * size, a + most * size, size);
heapify(a, size, most, heapSize, comp);
}
}
/*******************************************优先队列***********************************************/
// 数据结构:优先队列
typedef struct {
int length; // 队列的最大长度
int heapSize; // 队列当前元素个数
void *heap; // 队列头指针(堆的根节点指针),存储空间
} priorityQueue;
// 创建并初始化队列:入参size为队列元素的大小,用于适配不用类型的元素,n为队列元素个数
priorityQueue creatQueue(int size, int n)
{
priorityQueue q;
q.length = n;
q.heapSize = 0; // 队列初始化时,当前元素个数为0
q.heap = (void*)malloc(n * size); // 为当前队列申请内存
return q;
}
// 入队算法:e为入队元素指针,size为队列元素大小
void enterQueue(priorityQueue *q, int size, void *e, int(*compare)(void *, void *))
{
// 判断队列是否满
if (q->heapSize == q->length) {
return;
}
// 入队后,队列元素数量加一
int i = q->heapSize ++;
// 将待入队元素,复制到队列尾部
memcpy(q->heap + i * size, e, size);
// 调整:判断新增的尾部节点与其父节点的优先级,若尾部优先级高,则与父节点进行交换;并继续进行判断
while (i > 0 && compare(q->heap + parent(i) * size, q->heap + i * size) < 0) {
swap(q->heap + i * size, q->heap + parent(i) * size, size);
i = parent(i);
}
}
// 出队算法:返回队列中最优先元素
void *outQueue(priorityQueue *q, int size, int(*compare)(void *, void *))
{
// 判断队列非空
assert(q->heapSize >= 1);
// 保存待出队元素
void *top = (void*)malloc(size);
memcpy(top, q->heap, size);
// 出队后,队列元素数量减一
q->heapSize --;
// 将当前队尾元素覆盖队首元素(即待出队元素,且待出队元素已经保存)
memcpy(q->heap, q->heap + (q->heapSize) * size, size);
// 队首元素变更后,进行堆性质调整
heapify(q->heap, size, 0, q->heapSize, compare);
return top;
}
// 判断队列是否为空
int enpty(priorityQueue q)
{
return q.heapSize < 1;
}
// 清理队列环境
void clearQueue(priorityQueue *q)
{
free(q->heap);
q->heap = NULL;
q->heapSize = 0;
q->length = 0;
}
/*******************************************测试代码***********************************************/
int main(void)
{
int arr[] = {5, 1, 9, 4, 6, 2, 0, 3, 8, 7};
int arrSize = sizeof(arr) / sizeof(arr[0]);
priorityQueue q = creatQueue(sizeof(int), 10);
for (int i = 0; i < 10; i++) {
enterQueue(&q, sizeof(int), arr + i, intLess);
}
while(!enpty(q)) {
printf("%d ", *(int*)outQueue(&q, sizeof(int), intLess));
}
printf("\n");
clearQueue(&q);
while (1);
return 0;
}