分治算法四:基于二叉堆的优先队列

一、背景介绍

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;
}

三、测试结果

posted @ 2021-02-25 00:13  Pangolin2  阅读(77)  评论(0编辑  收藏  举报