队列
队列的特点大家都知道,是先进先出(FIFO),就和排队一样。
二叉堆
二叉堆是一种特殊的堆,其主要应用有两个,首先是一种排序方法——堆排序,第二是优先级队列。
二叉堆有两种:最大堆和最小堆。
- 最大堆:最大堆的堆顶是整个堆中的最大元素,父结点的键值总是大于或等于任何一个子节点的键值;
- 最小堆:最小堆的堆顶是整个堆中的最小元素,父结点的键值总是小于或等于任何一个子节点的键值。
二叉堆其实就是一种特殊的二叉树(完全二叉树,节点一层一层,从左到右按顺序排列),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。
我们在表示一个堆(大顶堆或小顶堆)的时候,实际上是通过一个一维数组来维护一个D叉树(d-ary heap),首先看下图的二叉树(d=2,d表示每个父节点最多有几个子节点),数字代表索引:
- 任意一个节点的父节点的索引为:(index - 1) / d
- 任意一个节点的左子节点的索引为:(index * d) + 1
- 任意一个节点的右子节点的索引为:(index * d) + 2
它的时间复杂度为O(logn·dn)。
二叉堆的主要操作就两个,sink(下沉)和swim(上浮)。
以大顶堆为例
swim(上浮)
如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮。
sink(下沉)
如果某个节点 A 比它的子节点(中的一个)小,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉。
优先队列(PriorityQueue)
优先队列不再遵循先入先出的原则:
- 最大优先队列,不管入队顺序,当前最大的元素优先出队。
- 最小优先队列,不管入队顺序,当前最小的元素优先出队。
为了实现这个目的,就需要新增加的元素进行排序,让最大或者最小的元素在队列顶部。因此可以使用二叉堆进行设计。
优先级队列有两个主要 API,分别是 insert 插入一个元素和 delMax 删除最大元素(如果底层用最小堆,那么就是 delMin)
insert 入队
入队时在二叉堆的堆底部增加新的元素,然后再进行上浮下沉操作,保持二叉堆的结构。
delMin 出队
出队时,删除二叉堆的堆顶元素,然后将堆底元素替换到顶部,再进行上浮下沉操作,保持二叉堆的结构。
C++的STL库中有优先队列,Java也提供了这种数据结构。
C# 在.Net 6当中也增加了优先队列,有大佬已经解析了源码,大概花个10分钟就能看完。
源码解析C#中PriorityQueue(优先级队列)的实现