普通的队列是一种FIFO结构,在优先队列(PriorityQueue)中,数据存在优先级,在进行出队操作时,具有最大(MaxPriorityQueue)或最小(MinPriorityQueue)优先级的元素最先出队。在很多应用场景中,都需要这种对数据进行有序处理或者按照优先级处理的方式
优先队列的应用很广泛,最常见的是进行任务调度,当有多个任务都需要处理的时候,为不同的任务划分优先级,并分别调度;优先队列还可以开发图搜索算法、数据压缩算法等
实现
优先队列的实现有很多方式,例如链表和数组。利用二叉堆来实现优先队列是一种较为高效的做法
结构 | 入队 | 出队最大元素 |
---|---|---|
有序数组 | N | 1 |
无序数组 | 1 | N |
堆 | logN | logN |
二叉堆实现的优先队列能够保证在插入和删除两个维度都较快
二叉堆
二叉堆是一组能够用堆有序的完全二叉树排列的元素,并在数组中按照层级存储
可以用下图来表示一个二叉堆
假设字幕A~Z表示的数值依次增大,则上图表示的是一个最大二叉堆(大根堆),根节点”T”最大,二叉堆中任意子节点的数值不大于父节点数值
二叉堆有以下特性
位置为k的节点父节点位置为k/2,两个子节点位置分别为2k和2k+1
大小为N的完全二叉树高度为logN
以上性质决定了二叉堆在进行遍历或者搜索的路径是跳跃层级的,无论是插入还是删除操作,由于树高最大logN,因此操作的复杂度最大也为logN
数组实现二叉堆
使用数组实现二叉堆是非常高效的,二叉堆中元素的位置和数组索引的关系可以用下图表示
使用数组实现二叉堆,仅利用数组索引就可以沿着树上下移动,非常便利。要注意为了编程时父子位置关系的统一性,数组的第一个位置不使用
在对堆进行操作时,会首先进行一些简单的改动,例如插入时先将元素插入到堆底,或者删除时先删除堆顶元素,这样会打破原有堆的有序状态,然后再将堆恢复有序状态(堆的有序化)。本文以最大优先队列为例,对堆的有序化操作进行说明
有序化需要用到两个辅助函数:比较(less)和交换(exch)。less函数是为了实现优先队列的泛化,希望实现的优先队列是一种泛型数据结构,而非某一种特定的数据结构,因此对于某一种具体的数据结构和类型,需要提供对应的比较函数”compare”,例如字符串String、文件File、时间Time等;交换两个元素是优先队列中使用非常频繁的操作,因此提取为单独的函数。以下是less和exch的伪代码
1 | boolean less(array, compare, i, j) |
上浮(swim)
当插入一个元素到堆底后,如果该元素比其父节点更大,就需要通过上浮操作来对堆进行有序化。例如在G节点下向堆中插入元素”Y”
“Y”元素只需要一遍一遍的与其父节点进行比较,并交换它们的位置,当”Y”元素到达合适的位置时,整个堆就变得有序了
整个过程是插入元素不断地向上”浮动“,不在上浮路径上的元素都保持不变。有序化后部分元素的所在层数会发生变化。上浮的伪代码如下
1 | // k: new item's index |
下沉(sink)
当某个元素变得比它的两个子节点或是其中之一小,通过下沉操作来恢复有序状态。例如将堆顶元素”T”删除后再将”G”元素放在原先”T”元素的位置
“G”元素只需要一遍一遍与其子节点进行比较,并交换位置,当”G”元素到达合适的位置时,整个堆就变得有序了
下沉的伪代码
1 | void sink(k) |
自动扩容
由于数组需要在创建时分配固定大小,因此为了提高利用的灵活性,需要队列能够自动调整数组大小。太过频繁的重新调整会增大开销,较为合理的方式是
当数组满时,扩容为原数组2倍
当数组元素大小减小到数组容量时,减小容量为原数组一半
C语言实现MinPQ
MinPQ
结构定义
1 | /* -- MinPQ --{*/ |
创建MinPQ
,创建函数需要传入节点大小和容量,如果容量为0,默认支持自动扩容;如果使用自定义优先级,需要传入比较器comparator
。创建函数内部会创建并维护一个内部默认的优先级数组p
,该数组是整型类型,如果要用默认的整型优先级,后续插入元素时可传入优先级数值
1 | /* |
插入元素,会首先检查容量和是否支持扩容;新节点先放在堆底,然后上浮操作进行有序化
1 | /* |
删除元素,先将堆顶元素拷贝出来,然后将堆底元素移动到堆顶,调用下沉sink
进行有序化
1 | err_type MinPQ_delmin(struct _minpq *q, void *u, int *p) |
获取指定索引数据,该函数可以结合for
循环遍历队列
1 | err_type MinPQ_get(struct minpq *q, void *u, int *p, int index) |
上浮操作,用到了递归
1 | static void _swim(struct _minpq *q, int k) |
下沉操作
1 | static void _sink(struct _minpq *q, int k) |
交换函数
1 | static void _swap(struct __minpq *q, int i, int j) |
比较函数
1 | static bool _greater(struct _minpq *q, int i, int j) |
扩容函数
1 | err_type _resize(struct _minpq *q, int capcaity) |
一些辅助查询函数
1 | int MinPQ_size(struct _minpq *q) |