优先队列 c语言,优先队列-C语言实现

我们之前已经介绍过队列-C语言实现,它们是先入先出的,这很容易用平常的排队来理解。但是如果这个队列要支持有紧急情况的人先出队呢?原先那种队列就不再适用了,我们需要使用本文所提到的特殊队列—优先队列。本文相关代码地址 github 。

优先队列

优先队列也是一种抽象数据类型。优先队列中的每个元素都有优先级,而优先级高(或者低)的将会先出队,而优先级相同的则按照其在优先队列中的顺序依次出队。

也就是说优先队列,通常会有下面的操作:

将元素插入队列

将最大或者最小元素删除

这样的话,我们完全可以使用链表来实现,例如以O(1)复杂度插入,每次在表头插入,而以O(N)复杂度执行删除最小元素;或者以O(N)复杂度插入,保持链表有序,而以O(1)复杂度删除。

然而 优先队列往往使用堆来实现 ,以至于通常说堆时,就自然而然地想到了优先队列。

二叉堆

二叉树堆是一棵 完全二叉树 ,并且对于每一个节点(根节点除外),它的父节点小于或等于它,这样最小元素就会在堆顶,我们就很容易找到最小元素。如果你还不清楚二叉树,建议先阅读《二叉树-C语言实现》。为了理解二叉堆的特性,还需要再介绍两个概念:

满二叉树:除叶子节点外,所有节点都有两个子节点,称为满二叉树。这个很容易理解,就不多做解释。

完全二叉树 :除了最后一层外,每层节点个数达到最大,并且最后一层的叶子节点都靠左边排列。

如下图一是一棵完全二叉树,而图二中的不是,因为最后一层的叶子节点不全在左边排列。

5c970cf4487096ecd8d23fb4039cb2dc.png

21def6e420eb397c86c3568d0606be19.png

二叉堆可以很容易用数组来表示,因为一棵高度为h的完全二叉树有2^h到2^(h+1)-1个节点,这样存放一个二叉堆就不会太浪费空间,而且一旦知道高度,就可以知道节点数的范围。

那么如何使用数组来表示二叉堆怎么存放元素呢?

对于数组i上的元素,它的左儿子在2i位置,右儿子2i+1的位置,那么它的父节点在[i/2]的位置。例如节点1位置的左儿子节点在2处。

本文位置0不存储数据

例如,对于下面的二叉堆(用字母表示的二叉堆),如果存储在数组中,则是下面这样:

54dceed949ba6a5b7cca65077d14e1b7.png

数组中存放情况:

0

1

2

3

4

5

6

不存储

a

b

c

d

e

f

二叉堆的操作

我们假设后面的操作都是让最小元素在堆顶,即对小堆操作。堆的常见操作有:

初始化

判断堆是否满

判断堆是否为空

向堆中插入元素

销毁堆

删除最小元素

找到最小元素

初始化堆

初始化堆之前,先定义堆结构。

typedef struct HeapStruct

{

int capacity; //最大元素数量

int size; //堆元素数量

ElementType *eles; //堆元素数组

}PriorityQueue;

这里定义了HeapStruct结构,包含三个元素,分别是最大容量,当前堆大小,以及堆数组。

因为这里使用的是动态数组,所以我们需要对其进行初始化,当然你也可以参考《如何自己实现一个栈》使用静态数组来实现,但这种方式的缺点很明显,它只能固定堆大小。

堆初始化函数如下:

PriorityQueue *init_PQ(int maxEleNum)

{

PriorityQueue *pq = NULL;

/*检查输入大小的合法性*/

if(maxEleNum <= 0 )

return NULL;

pq = malloc(sizeof(PriorityQueue));

if(NULL == pq)

{

printf("malloc failed\n");

return NULL;

}

/*下标为0的位置保留,不作使用*/

pq->eles = malloc((maxEleNum + 1)*sizeof(ElementType));

if(NULL == pq->eles)

{

printf("malloc failed\n");

free(pq);

return NULL;

}

/*初始化成员*/

memset(pq->eles,0,(maxEleNum + 1)*sizeof(ElementType));

pq->capacity = maxEleNum;

pq->size = 0;

return pq;

}

主要做了以下几件事:

创建一个空堆

初始化元素数量为0

堆是否已满

判断堆是否已满只需要判断容量和当前大小的比较结果即可:

int pq_is_full(PriorityQueue *pq)

{

if(NULL == pq)

return false;

if(pq->capacity == pq->size)

return true;

else

return false;

}

堆是否已空

判断堆是否为空只需要判断它的size是否为0即可:

int pq_is_empty(PriorityQueue *pq)

{

if(NULL == pq)

return false;

if(0 == pq->size)

return true;

else

return false;

}

堆的插入

按照我们前面的分析,插入操作是比较容易,放在属于它的下标位置即可,但是为了保持堆的性质,即节点的值要大于等于它的父节点,插入时就需要考虑更多了。

我们可以采取这样的方式:

将元素准备插入到下一个空闲位置(空穴)

如果插入后,仍然保持堆得性质,则直接插入该位置

如果插入后,导致父节点不再小于等于它,则将父节点值移到该空穴,父节点原来的位置就变成空穴

继续尝试将心得元素放入上面的空穴,并与父节点比较,知道新元素找到属于它的位置

举个例子,假如要在下面的二叉堆中,再插入2:

3673ed235cd7e3a78d5a395a7aefbeca.png

首先把2放在完全二叉树的最后一个位置,即前面提到的空闲位置,如下图:

7407c462a40a8bc95517f45ee2ab1592.png

由于2比它的父节点5要小,如果插在这里,则不满足堆性质,因此,需要交换它和父节点的位置:

caab8e5570d2a5ff7457a7f2b2ffcfc7.png

此时,发现2所在位置仍然比它的父节点要小,因此,还需要和它的父节点交换位置:

90a98c78ed436967af547eb25a1d509d.png

最终状态则满足堆得性质,即父节点总是小于等于它的子节点。

代码实现如下:

int insert_pq(ElementType value,PriorityQueue *pq)

{

int i =0;

/*确保优先队列没有满*/

if(pq_is_full(pq))

{

printf("priorityQueue is full\n");

return FAILURE;

}

printf("insert %d\n",value);

/*不断和父节点探测比较,直到找到属于它的位置*/

for(i = pq->size+1;pq->eles[i/2] > value && i > 1;i/=2)

{

pq->eles[i] = pq->eles[i/2];

}

pq->eles[i] = value;

pq->size++;

return SUCCESS;

}

建立N个元素的二叉堆的时间复杂度为O(N)。

找到最小元素

由于我们在插入的时候就保证了堆的性质,因此找到最小元素是非常容易的,因为它就是位于堆顶,因此代码实现如下:

int find_min(PriorityQueue *pq,ElementType *value)

{

if(pq_is_empty(pq))

{

printf("priorityQueue is empty\n");

return FAILURE;

}

/*0处的元素作为哨兵没有使用*/

*value = pq->eles[1];

return SUCCESS;

}

删除最小元素

删除与插入相反,删除的是堆顶元素,我们需要找到一个元素来替代堆顶的位置,以保证堆的性质不被破坏。因此进行如下的操作:

删除堆顶元素,堆顶成为空穴

由于直接将最后的元素放入前面的空穴可能破坏堆性质,因此将较小的儿子插入空穴,该儿子的位置变成空穴,这样空穴就下滑了一层

重复上面的过程,空穴不能再下滑,将最后的元素放入该空穴

还是以前面建立的二叉堆为例,假如要删除堆顶的2。则直接先把2删除,那么2的位置就有一个空穴。

ec75ec5ba30e284f56062cf1b69cbab6.png

这个时候,我们将它的两个子节点中较小的一个,移动到堆顶位置:

46617a60d72aa968f5c35c19b9244582.png

最后继续将空穴位置处它的子节点较小的一个,移动到空穴位置:

e727dbf1f7ff13fe10a9c95863a56f30.png

最终删除了堆顶元素。

代码实现如下:

int delete_min(PriorityQueue *pq,ElementType *min)

{

int i = 1;

int minChild =0;

if(pq_is_empty(pq))

{

printf("priorityqueue is empty\n");

return FAILURE;

}

/*取得最小值*/

*min = pq->eles[1];

/*暂时取出最后的元素*/

ElementType last = pq->eles[pq->size];

pq->size--;

if(0 == pq->size)

{

pq->eles[i] = 0;

return SUCCESS;

}

/*不断将空穴下滑*/

for(i = 1;i * 2 <= pq->size;i = minChild)

{

minChild = i * 2;

/*找到更小的孩子*/

if(minChild != pq->size && pq->eles[minChild+1] < pq->eles[minChild])

minChild+=1;

/*如果最后一个元素比空穴处的小儿子大,则继续下滑空穴,将该孩子上滤*/

if(last >pq->eles[minChild])

pq->eles[i] = pq->eles[minChild];

/*否则说明last放的位置不会破坏堆性质,则直接退出循环*/

else

break;

}

/*将最后的元素放在空穴位置*/

pq->eles[i] = last;

return SUCCESS;

}

删除操作的平均时间复杂度为O(logN)

完整代码运行结果

完整代码见开头说明的地址,运行结果如下:

insert 3

insert 4

insert 5

insert 6

insert 8

insert 2

priorityQueue is full

priorityQueue is full

the arr value is: 2 4 3 6 8 5

pq size is 6

the min is 2

the min is 3

the min is 4

the min is 5

the min is 6

the min is 8

destory pq success

观察删除最小元素的结果,有没有发现什么呢?

总结

本文介绍了优先队列最常见的实现方式-二叉堆实现,并且介绍了二叉堆地创建,插入和删除等基本操作。而典型的TOP k问题也非常适合使用堆来解决,本文不做介绍。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您解答这个问题。 首先,让我们简单介绍一下什么是优先队列和01背包问题。 优先队列是一种数据结构,它可以维护一组元素,并按照一定的优先级对它们进行排序。在优先队列中,每个元素都有一个与之关联的优先级,队列会按照优先级从高到低对元素进行排序。 01背包问题是指在给定的一组物品中,选择一些物品放进容量为V的背包中,使得放进背包中的物品总价值最大。其中,每个物品只有一个,可以选择放或不放。 那么,如何使用优先队列解决01背包问题呢?我们可以使用贪心算法,每次从优先队列取出价值最高的物品放入背包中,直到背包装满或优先队列为空。 下面是使用C语言实现的代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_N 100 // 物品的最大数量 #define MAX_V 10000 // 背包的最大容量 typedef struct { int v; // 物品的体积 int w; // 物品的价值 } item; // 用于比较两个item的优先级 int cmp(const void* a, const void* b) { item* x = *(item**)a; item* y = *(item**)b; double ratio_x = (double)x->w / x->v; double ratio_y = (double)y->w / y->v; if (ratio_x > ratio_y) { return -1; } else if (ratio_x < ratio_y) { return 1; } else { return 0; } } // 使用优先队列解决01背包问题 int knapsack(item* items[], int n, int capacity) { int i; int used = 0; int value = 0; // 定义一个优先队列 item** pq = malloc(n * sizeof(item*)); // 将物品按照单位价值从高到低排序 for (i = 0; i < n; i++) { pq[i] = items[i]; } qsort(pq, n, sizeof(item*), cmp); // 依次将优先队列中的物品放入背包中 for (i = 0; i < n; i++) { if (used + pq[i]->v <= capacity) { used += pq[i]->v; value += pq[i]->w; } else { break; } } free(pq); return value; } int main() { int n, capacity; item items[MAX_N]; int i; // 读入物品的数量和背包的容量 scanf("%d %d", &n, &capacity); // 读入每个物品的体积和价值 for (i = 0; i < n; i++) { scanf("%d %d", &items[i].v, &items[i].w); } // 使用优先队列解决01背包问题 int max_value = knapsack(items, n, capacity); // 输出结果 printf("%d\n", max_value); return 0; } ``` 以上就是使用C语言实现优先队列解决01背包问题的代码。希望能帮到您!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值