优先队列(priority queue)

时间:2022-05-27 17:38:03

定义

优先队列是一种支持以下主要操作的抽象数据结构:

Insert(p):添加一个带有优先权p的新元素
ExtractMax():取出具有最大优先权的元素

其他操作:
Remove(it):删除it迭代器指向的一个元素
GetMax():返回具有最大优先权的元素(不改变元素集合)
ChangePriority(it,p):把it迭代器指向的元素权限改为p

使用优先队列的算法

Dijsktra’s algorithm
Prims’ algorithm
Huffman’s algorithm
Heap Sort

优先队列的实现

二叉堆

定义

二叉堆是每个结点值大于等于它的孩子结点值的二叉树(每个结点只有0、1或2个孩子结点)。也就是树的每一条枝上的结点值不小于它的孩子结点。

基本操作

GetMax()

直接返回根节点的值

Insert(it, p)

1.在任意一个叶子结点插入新结点(这会引起堆属性的变化)
2.调整。新结点上移,也就是问题结点和与它连接的父母结点交换直到满足堆的属性

ExtractMax()

1.利用任意一个叶子结点替换根节点(这会引起堆的属性发生变化)
2.调整。问题结点下移,也就是问题结点和与它连接的较大孩子结点交换,直到满足堆的属性

ChangPriority()

1.更改指定结点的优先级(这会引起堆属性的变化)
2.根据指定结点的优先级是增大还是减小,上移或下移问题结点,直至满足堆的属性

Remove()

1.把要删除的结点的优先级更改为无穷大(这会引起堆的属性发生变化)
2.调整。问题结点上移,也就是把要删除的结点和与它连接的父母节点交换,直至满足堆的属性
调用ExtractMax()删除根节点

总结
时间复杂度
GetMax() O(1)
Insert() O(n)
ExtractMax() O(n)
ChangePriority() O(n)
Remove O(n)

完全二叉树

定义

1.二叉树
2.最后一层的结点居于最左端
3.其它层结点数达到最大

优势

1.树的高度低
一颗包含n个结点的完全二叉树最大高度为O(log n)
2.可以存成数组
parent(i) = i/2(向下取整)
LeftChild(i) = 2 * i
RightChild(i) = 2 * i + 1

缺点

保持完全二叉树的属性

插入一个元素:把要插入的结点当做叶子结点插入到最后一层的最左端空白位置,然后上移元素
删除最大值:用最后的叶子结点替代根节点,然后下移替代根节点之后的叶子结点

伪代码实现

基本设置

1.maxSize:堆中元素个数的最大值
2.size:堆的大小
3.H[1…maxSize]:长度为maxSize的数组,用来保存堆

parent(i)

return i/2

LeftChild(i)

return 2 * i

RightChild(i)

return 2 * i + 1

SiftUp(i)

#循环方式实现

while i > 1 and H[i] > H[parent(i)]:
    swap H[i] and H[parent(i)]
    i <--- parent(i) 

#递归函数实现

if i > 1 and H[i] > H[parent(i)]
    swap H[i] and H[parent(i)]
    i <--- parent(i)
    SiftUp(i)

SiftDown(i)

#循环实现

while true
    maxIndex <--- i
    l <---- LeftChild(i)
    if l <= size and H[l] > H[i]
        maxIndex <--- l
    r <--- RightChild(i)
    if r <= size and H[r] > H[maxIndex]
        maxIndex <--- r
    if i != maxIndex
        swap H[i] and H[maxIndex]
    else 
        break

#递归函数实现

#找出该结点的较大孩子结点
maxIndex <--- i
l <--- LeftChild(i)
if l <= size and H[l] > H[maxIndex]
    maxIndex <--- l
r <--- RightChild(i)
if r <= size and H[r] > H[maxIndex]
    maxIndex <--- r
if i != maxIndex
    swap H[i] and H[maxIndex]
    SiftDown(maxIndex)

Insert(p)

if size = maxSize
    return ERROR
else
    size <--- size + 1
    H[size] <--- p
    SifUp(size)

ExtractMax()

if size = 1
     return ERROR
else
     result <--- H[1]
     H[1] <--- H[size]
     size <--- size - 1
     SiftDown(1)
     return result

remove(i)

H[i] <--- 1000000
SiftUp(i)
ExtractMax()

ChangePriority(i,p)

oldp <--- H[i]
H[i] <--- p
if oldp > p
     SiftDown(i)
if oldp < p
     SiftUp(i)

总结

实现结果:

1.快速。所有操作的时间复杂度为O(log n)(GetMax()的时间复杂度甚至为O(1))
2.空间利用率高。在数组中只存储优先级,不存父母结点和孩子结点之间的连接关系
3.容易实现。所有的操作用几行代码就可以实现。

应用

使用优先级队列排序

HeapSort(A)(不考虑空间使用)

1.创建一个空的优先队列
2.for i from 1 to n
Insert(i)
3.for i from n downto 1
A[i] <——– ExtractMax()

上述算法的特点:

1.基于比较且时间复杂度为O(n * log n)
2.自然生成选择排序,使用良好的数据结构替代了以往扫描整个数组寻找最大数的方法
3.使用了额外的内存空间

把数组转换成堆

主要思路:从下至上的调整修正堆的性质

1.首先,所有的叶子结点都是满足堆的性质的
2.然后,从深度为1的子树开始调整修正堆的性质
3.依次遍历,直到根节点时,整棵树都满足堆的性质
算法实现:(时间复杂度为n * log n)
BuildHeap(A)

size <--- n
for i from n/2 downto 1
    SiftDown(i)

HeapSort(A)(限制存储空间的使用)

BuildHeap(A)
repeat (n - 1)times:
    swap A[1] and A[size]
    size <--- size - 1
    SiftDown(1)

总结
此种堆排序和上面第一种相比,时间复杂度是相同的均为 O(n * log n),但是第二种堆排序不需要额外的内存空间

备注
以0为起始搜引号的数组:

parent(i)
    return (i-1)/2
LeftChild(i)
    return 2 * i + 1
RightChild(i)
return 2 * i +2

最小二叉堆:二叉堆是每个结点值大于等于它的孩子结点值的二叉树(每个结点只有0、1或2个孩子结点)。也就是树的每一条枝上的结点值不大于它的孩子结点。

d叉堆:和二叉堆类似,不同之处在于d叉堆的每个结点的孩子结点树最多有d个,对应树的高度为O(logd n)