队列有一个重要的变体,叫作优先级队列。和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的。优先级最高的元素在最前,优先级最低的元素在最后。因此,当一个元素人队时,它可能直接被移到优先级队列的头部。
你或许可以想到一些使用排序函数和列表实现优先级队列的简单方法。但是,就时间复杂度而言,列表的插人操作是0(n),排序操作是O(nlogn)。其实,效率可以更高。实现优先级队列的经典方法是使用叫作二叉堆的数据结构。二叉堆的人队操作和出队操作均可达到0(logn)。
首先实现enqueue方法。将元素加入列表的最简单、最高效的方法就是将元素追加到列表尾。追加操作的优点在于,它能保证完全树的性质,但缺点是很能会破坏堆的结构性质可以写一个方法,通过比较新元素与其父元素来重新获得堆的结构性质。如果新元素小于其父元素,就将二者交换。
注意,将元素往上移时,其实是在新元素及其父元素之间重建堆的结构性质。此外,也保留
了兄弟元素之间的堆性质。当然,如果新元素很小,需要继续往上一层交换。下面列出了percUp方法的代码,该方法将元素一直沿着树向上移动,直到重获堆的结构性质。我们使用整数除法计算任意节点的父节点。就当前节点而言,父节点的下标就是当前节点的下标除以2。
class priority_queue:
def __init__(self,heap):
self.heap=heap
def percup(self,i):
while i//2>0:
if self.heap[i]<self.heap[i>>1]:
temp=self.heap[i]
self.heap[i]=self.heap[i>>1]
self.heap[i>>1]=temp
i=i>>1
向二叉堆中添加新元素
def enqueue(self,k):
self.heap.append(k)
self.percup(len(self.heap)-1)
定义enqueue方法后,就可以编写dequeue方法。既然堆的结构性质要求根节点是树的最小元素,那么查找最小值就很简单。dequeue方法的难点在于,如何在移除根节点之后重获堆的结构性质和有序性。可以分两步重建堆。第一步,取出列表中的最后一个元素,将其移到根节点的位置。移动最后一个元素保证了堆的结构性质,但可能会破坏二叉堆的有序性。第二步,将新的根节点沿着树推到正确的位置,以重获堆的有序性。
为了维持堆的有序性,只需交换根节点与它的最小子节点即可。重复节点与子节点的交换过程,直到节点比其两个子节点都小。
def percdown(self,i):
while (i<<1)<=len(self.heap)-1:
num=self.minChild(i)
if self.heap[i]>self.heap[num]:
temp=self.heap[i]
self.heap[i]=self.heap[num]
self.heap[num]=temp
def minChild(self,i):
if i<<1==len(self.heap)-1:
return i<<1
elif self.heap[i<<1]<self.heap[i<<1|1]:
return i<<1
return i<<1|1
从二叉堆中删除最小的元素即出队操作
def dequeue(self):
temp=self.heap[1]
self.heap[1]=self.heap[-1]
self.heap.pop()
self.percdown(1)
return temp
根据元素列表构建原始堆
def build_priority_queue(self):
self.heap.insert(0,0)
for i in range((len(self.heap)-1)>>1,0,-1):
self.percdown(i)