1.优先队列(Priority Queue)
前面我们学习了一种FIFO数据结构队列。队列有一种变体称为“优先队列”。如银行窗口取号排队,VIP客户可以插到队首。
- 优先队列的出队跟队列一样从队首出队
- 在优先队列内部,数据项的次序是由“优先级”来确定。
高优先级的数据项排在队首,而低优先级的数据项则排在后面。这样,优先队列的入队操作就比较复杂,需要将数据项根据其优先级尽量挤到队列前方。
2. 二叉堆(Binary Heap)实现优先队列
实现优先队列的经典方案是采用二叉堆数据结构。二叉堆能够将优先队列的入队和出队的复杂度都保持在O(logn)
二叉堆的有趣之处在于,其逻辑结构上像二叉树,却是用非嵌套的列表来实现的。最小key排在队首的称为“最小堆min heap”,反之,最大key排在队首的是“最大堆max heap”
ADT BinaryHeap的操作定义如下:
- BinaryHeap():创建一个空二叉堆对象
- insert(k):将新key加入到堆中
- findMin():返回堆中的最小项,最小项仍保留在堆中
- delMin():返回堆中的最小项,同时从堆中删除
- isEmpty():返回堆是否为空
- buildHeap(list):从一个key列表创建新堆
3.用非嵌套列表实现二叉堆
为了使堆操作能保持在对数水平上,就必须采用二叉树结构;同样,如果要使操作始终保持在对数数量级上,就必须始终保持二叉树的“平衡”即树根左右子树拥有相同数量的节点
我们采用“完全二叉树”的结构来近似实现“平衡”
完全二叉树,叶节点最多只出现在最底层和次底层,而且最底层的叶节点都连续集中在最左边,每个内部节点都有两个子节点,最多可有1个节点例外。
3.1 完全二叉树的列表实现及性质
完全二叉树由于其特殊性,可以用非嵌套列表以简单的方式实现,具有很好的性质。如果节点的下标为p,那么其左子节点下标为2p,右子节点为2p+1,其父节点下标为p//2
3.2堆次序(Heap Order)
任何一个节点x,其父节点p的key均小于x中的key。这样,符合“堆”性质的二叉树,其中任何一条路径都是一个已排数列,根节点的key最小。
3.3二叉堆的python实现
3.3.1 二叉堆初始化
采用一个列表来保存堆数据,其中表首下标为0的项无用,但为了后面代码可以用到简单的整数除法,仍保留它。
class BinHeap:
def __init__(self):
self.heapList=[0]
self.currentSize=0
3.3.2 insert(key)方法
首先,为了保持“完全二叉树”的性质,新key应该添加到列表末尾。如下图所示,显然无法保持“堆”次序,虽然对其他路径的次序没有影响,但对于其到根节点的路径(红虚线框出的路径)可能破坏次序。需要将新key沿着路径来“上浮”到其正确位置。
注意:新key的“上浮”不会影响其他路径节点的“堆”次序
python代码实现
#insert代码
def percUp(self,i):#上浮
while i//2>0:
if self.heapList[i]<self.heapList[i//2]:#当前节点的值小于父节点,进行交换
tmp=self.heapList[i//2]
self.heapList[i//2]=self.heapList[i]
self.heapList[i]=tmp
i=i//2#沿路径上浮
def insert(self,k):
self.heapList.append(k)#添加到末尾
self.currentSize=self.currentSize+1
self.percUp(self.currentSize)#新key上浮
3.3.3 delMin()方法
移走整个堆中最小的key即根节点heapList[1]。为了保持“完美二叉树”的性质,只用最后一个节点来代替根节点。同样,这么简单的替换还是破坏了“堆”次序
解决方法:将新的根节点沿着一条路径“下沉”,直接比两个字节点都小
“下沉”路径的选择:如果比子节点大,那么选择比较小的子节点交换下沉
python代码实现
#delMin()代码
def percDown(self,i): #下沉操作
while (i*2)<=self.currentSize:#向下
mc=self.minChild(i)
if self.heapList[i*2]>self.heapList[mc]:
tmp=self.heapList[i]
self.heapList[i]=self.heapList[mc]
self.heapList[mc]=tmp
i=mc
def minChild(self,i):#更小的子节点
if i*2+1>self.currentSize:
return i*2#唯一子节点
else:
if self.heapList[i*2]<self.heapList[i*2+1]:#返回较小的
return i*2
else:
return i*2+1
def delMin(self):
retval=self.heapList[1]#移走堆顶
self.heapList[1]=self.heapList[self.currentSize]
self.currentSize=self.currentSize-1
#新顶下沉
self.heapList.pop()
self.percDown(1)
return retval
3.3.4 buildHeap(lst)方法:从无序表生成“堆”
我们最自然的想法是:用insert(key)方法将无序表中的数据项逐个insert到堆中,但这么做的总代价是O(nlogn)。其实用“下沉”法能够将总代价控制在O(n)
python代码实现
#buildHeap(lst)代码
def buildHeap(self,alist):
i=len(alist)//2#从最后节点的父节点开始,因为叶节点无需下沉
self.currentSize=len(alist)
self.heapList=[0]+alist[:]
print(len(self.heapList),i)
while (i>0):
print(self.heapList,i)
self.percDown(i)
i=i-1
print(self.heapList,i)