最小堆 / 优先队列(C语言实现)

来源:互联网 发布:淘宝无线端链接生成 编辑:程序博客网 时间:2024/05/01 19:25

最近找实习,复习下数据结构方面的内容。

完全二叉树有两种形态,一种是:二叉树的所有子树要么没有孩子,要么一定有左孩子。另一种是:二叉树要么没有子树,要么一定左右子树都有。

堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左孩子和右孩子节点的值。

最大堆和最小堆是二叉堆的两种形式。

最大堆:根结点的键值是所有堆结点键值中最大者。

最小堆:根结点的键值是所有堆结点键值中最小者。

在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高进先出 (largest-in,first-out)的行为特征。优先队列常用于模拟操作系统的进程调度,可以用堆来实现。

下面我们用数组来实现一个最小堆。

代码如下:

MinHeap.h

#ifndef DataStructures_MinHeap_h#define DataStructures_MinHeap_hstruct MinHeap;typedef struct MinHeap * MinPriorityQueue;typedef int ElementType;// 初始化堆MinPriorityQueue initialize(int maxElements);// 销毁堆void destroy(MinPriorityQueue pqueue);// 清空堆中的元素void makeEmpty(MinPriorityQueue pqueue);// 插入操作void insert(ElementType x, MinPriorityQueue pqueue);// 删除最小者操作,返回被删除的堆顶元素ElementType deleteMin(MinPriorityQueue pqueue);// 查找最小者(堆顶)ElementType findMin(MinPriorityQueue pqueue);// 判断堆是否为空int isEmpty(MinPriorityQueue pqueue);// 判断堆是否满int isFull(MinPriorityQueue pqueue);// 通过一个数组来建堆,相当于将用数组实现的无序树转换为堆序树MinPriorityQueue buildHeap_insert(int *arr, int n);MinPriorityQueue buildHeap_percolate(int *arr, int n);// 打印堆void printMinPriorityQueue(MinPriorityQueue pqueue);#endif

MinHeap.c

#include <stdio.h>#include <stdlib.h>#include "MinHeap.h"/* 标记节点,类似于链表中的表头节点 * 该值必须小于所有最小堆中的元素,设其值为-1 */#define SentinelElement -1/* * 使用数组实现堆 * * capacity 数组的最大容量 * size     数组的长度 * elements 堆中的元素存放的数组 */struct MinHeap{    int capacity;    int size;    ElementType *elements; // 堆的元素个数为size,实际上用来存储的数组的长度为size + 1,还包括一个sentinel元素};voidPQueueNULLWarning(){    printf("Warning: Minimum Priority Queue is NULL");}voidoutOfSpaceFatalError(){    printf("Fatal Error: Out of space");    abort();}MinPriorityQueueinitialize(int maxElements){    MinPriorityQueue pqueue;        if (maxElements <= 0)    {        printf("Fail to initialize: maxElements <= 0");        return NULL;    }        pqueue = malloc(sizeof(struct MinHeap));    if (pqueue == NULL)        outOfSpaceFatalError();        // 数组的第0个元素是个sentinel标记节点,计入数组容量中,但不计入capcaity或size中    pqueue->size = 0;    pqueue->capacity = maxElements;    pqueue->elements = malloc(sizeof(ElementType) * (pqueue->capacity + 1));    if (pqueue->elements == NULL)        outOfSpaceFatalError();    else        pqueue->elements[0] = SentinelElement;        return pqueue;}voiddestroy(MinPriorityQueue pqueue){    if (pqueue != NULL)    {        // 在GNU99标准中,free(NULL)什么都不做直接返回,所以不用判断pqueue->elements是否为NULL        free(pqueue->elements);        free(pqueue);    }}voidmakeEmpty(MinPriorityQueue pqueue){    if (pqueue != NULL)        pqueue->size = 0;    else        PQueueNULLWarning();}/* * 插入时,堆中的元素执行下滤操作 * 删除时,堆中的元素执行上滤操作 *//* * 插入的时间复杂度为O(log N),N为最小堆中的元素个数 * 实际上,其平均执行时间为O(1) */voidinsert(ElementType x, MinPriorityQueue pqueue){    if (pqueue == NULL)        PQueueNULLWarning();        if (isFull(pqueue))    {        printf("Fail to insert: Priority Queue is Full");        return;    }    else    {        int i;                // sentinel element在这里作为elements[0]被比较,是循环的终止条件        for (i = ++pqueue->size; x < pqueue->elements[i / 2]; i /= 2)            pqueue->elements[i] = pqueue->elements[i / 2]; // 下滤操作        pqueue->elements[i] = x;    }}/* * 删除操作的平均时间为O(log N) */ElementTypedeleteMin(MinPriorityQueue pqueue){    if (pqueue == NULL)    {        PQueueNULLWarning();        return SentinelElement;    }        if (isEmpty(pqueue))    {        printf("Fail to delete: Priority Queue is Empty");        return SentinelElement;    }        int i, child;    ElementType minElement, lastElement;        // 注意对某个节点进行上滤操作时,要判断该节点是有两个儿子还是一个儿子    minElement = pqueue->elements[1];    lastElement = pqueue->elements[pqueue->size--];    for (i = 1; i * 2 <= pqueue->size; i = child)    {        child = i * 2;                // 节点i只有一个儿子时必有i * 2 = pqueue->size        if (child < pqueue->size && pqueue->elements[child] > pqueue->elements[child + 1])            child++;                if (lastElement < pqueue->elements[child])            break;        else            pqueue->elements[i] = pqueue->elements[child]; // 上滤操作    }    pqueue->elements[i] = lastElement;        return minElement; // 返回被删除的元素}/* * 执行时间:O(1) */ElementTypefindMin(MinPriorityQueue pqueue){    if (pqueue == NULL)    {        PQueueNULLWarning();        return SentinelElement;    }    else        return pqueue->elements[1];}intisEmpty(MinPriorityQueue pqueue){    if (pqueue == NULL)    {        PQueueNULLWarning();        return -1;    }    else        return (pqueue->size == 0);}intisFull(MinPriorityQueue pqueue){    if (pqueue == NULL)    {        PQueueNULLWarning();        return -1;    }    else        return (pqueue->size == pqueue->capacity);}voidpercolateDown(int *arr, int len, int i){    int child;    int n = len - 1;    ElementType tmp;        for (tmp = arr[i]; i * 2 < n; i = child)    {        child = i * 2;        if (child < n && arr[child] > arr[child + 1])            child++;                if (tmp > arr[child])            arr[i] = arr[child];        else            break;    }    arr[i] = tmp;}MinPriorityQueuebuildHeap_percolate(int *arr, int n){    if (arr == NULL)    {        printf("Error: Array is NULL");        return NULL;    }        MinPriorityQueue pqueue;    pqueue = malloc(sizeof(struct MinHeap));        if (pqueue == NULL)        outOfSpaceFatalError();    ElementType *elements = malloc(sizeof(ElementType) * (n + 1));    if (elements == NULL)        outOfSpaceFatalError();        int i;    for (i = 1; i <= n; i++)        elements[i] = arr[i - 1];    elements[0] = SentinelElement;        for (i = n / 2; i > 0; i--)        percolateDown(elements, n + 1, i);        pqueue->elements = elements;    pqueue->size = n;    pqueue->capacity = n * 2;        return pqueue;}/* * 通过n次插入元素建立堆,由于每次插入的平均执行时间为O(1),所以建堆平均时间为O(N) */MinPriorityQueuebuildHeap_insert(int *arr, int n){    MinPriorityQueue pqueue;        if (arr == NULL)    {        printf("Array is NULL, fail to build heap");        return NULL;    }        pqueue = initialize(n * 2);    for (int i = 0; i < n; i++)        insert(arr[i], pqueue);        return pqueue;}voidprintMinPriorityQueue(MinPriorityQueue pqueue){    if (pqueue == NULL)    {        PQueueNULLWarning();        return;    }        if (pqueue->elements == NULL)    {        printf("Fail to print: Elements of priority queue is NULL");        return;    }        if (isEmpty(pqueue))    {        printf("Empty Prioirty Queue\n");        return;    }        printf("Priority Queue\n");    for (int i = 1; i <= pqueue->size; i++)        printf("Element[%d] = %d\n", i, pqueue->elements[i]);    printf("\n");}

建堆的测试代码:

voidbuildHeapTest(){    int a[9] = {9, 8, 7, 6, 5, 4, 3, 2, 1};        MinPriorityQueue pqueue_ins = buildHeap_insert(a, 9);    MinPriorityQueue pqueue_per = buildHeap_percolate(a, 9);    printMinPriorityQueue(pqueue_ins);    printMinPriorityQueue(pqueue_per);}int main(int argc, const char * argv[]){    buildHeapTest();        return 0;}

分别使用插入和下滤两种方式建堆,所以建立的结果是不同的,输出如下:
Priority QueueElement[1] = 1Element[2] = 2Element[3] = 4Element[4] = 3Element[5] = 7Element[6] = 8Element[7] = 5Element[8] = 9Element[9] = 6Priority QueueElement[1] = 1Element[2] = 2Element[3] = 3Element[4] = 6Element[5] = 5Element[6] = 4Element[7] = 7Element[8] = 8Element[9] = 9


最大堆实现类似。

下面用最小堆解决“在数组中寻找第k大元素”的问题。

测试代码如下:

voidfindKMaxElementTest(int k){    int a[10] = {20, 10, 11, 21, 36, 90, 100, 200, 25, 9};        MinPriorityQueue pqueue = buildHeap_insert(a, 10);    for (int i = 1; i < k; i++)        deleteMin(pqueue);        printf("第%d大元素为:%d\n", k, findMin(pqueue));}int main(int argc, const char * argv[]){    findKMaxElementTest(5);        return 0;}

由于建堆最多需要花费O(N)的时间,每次删除操作最多需要花费O(log N)的时间,所以该算法的时间复杂度为O(N + klog N),如果k = [N / 2],那么时间复杂度为Θ(N log N)


主要参考了Mark Allen Weiss的《数据结构与算法分析 —— C语言描述》。


0 0
原创粉丝点击