一、优先队列(Priority Queue)
队列数据结构有一种变体称为“优先队列”。它的出队跟队列一样从队首出队;但在优先队列内部,数据项的次序却是由“优先级”来确定:高优先级的数据项排在队首,而低优先级的数据项则排在后面。这样,优先队列的入队操作就比较复杂,需要将数据项根据其优先级尽量挤到队列前方。
例如:银行窗口取号排队,VIP客户可以插到队首;操作系统中执行关键任务的进程或用户特别指定进程在调度队列中靠前。
实现优先队列的经典方案是采用二叉堆数据结构,二叉堆能够将优先队列的入队和出队复杂度都保持在 O ( log n ) O(\log n) O(logn)。
二、二叉堆(Binary Heap)
二叉堆的有趣之处在于,其逻辑结构上像二叉树,却是用非嵌套的列表来实现的!其中,最小数据项排在队首的称为“最小堆min heap”;反之,最大数据项排在队首的是“最大堆max heap”。
为了使堆操作的时间复杂度能保持在对数水平上, 所以必须采用二叉树结构;同样, 如果要使操作始终保持在对数数量级上, 就必须始终保持二叉树的“平衡”(这种二叉树也叫“平衡二叉树”:树根左右子树拥有相同数量的节点)。所以,我们采用“完全二叉树”的结构来近似实现“平衡”。
1. 完全二叉树
完全二叉树的叶节点最多只出现在最下面的两层,而且最底层的叶节点都连续集中在左边。
完全二叉树由于其特殊性,可以用非嵌套列表,以简单的方式实现,具有很好性质:如果节点的下标为p,那么其左子节点下标为2p,右子节点为2p+1,其父节点下标为p//2。
比如,上图中的完全二叉树(也可以看作二叉堆),数据项为9的节点,其下表为2,那么,他的左子节点下标为2*2=4,右子节点下标为2*2+1=5。
2. 堆次序(Heap Order)
任何一个节点x,其父节点中的值均小于x中的值。这样,符合“堆”性质的二叉树,其中任何一条从上到下的路径,均是一个已从小到大排序的数列,根节点的值最小。
三、二叉堆的python实现
__init__
初始化方法:我们采用一个列表来保存堆数据,其中表首下标为0的项无用(因为下标是从1开始的),但为了后面代码可以用到简单的整数乘除法,仍保留它。
class Binheap:
def __init__(self):
self.heapList = [0]
self.currentSize = 0
insert
插入方法:首先,为了保持“完全二叉树”的性质,新插入到列表末尾的数据项,需要将新数据项沿着路径“==上浮”==到正确的位置。(注意:新数据项的“上浮”不会影响其它路径节点的“堆”次序)
def percUp(self, i):
while i // 2 > 0: # 当父节点下标大于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 # 堆的大小加1
self.percUp(self.currentSize) # 上浮,比较大小
delMin
方法,移除整个堆中最小的数据项:根节点heapList[1]。
为了保持“完全二叉树”的性质,只用最后一个节点来代替根节点。然后将新的根节点“下沉”,直到比两个子节点都小。“下沉”路径的选择:如果比子节点大,那么选择较小的子节点交换下沉。
def percDown(self, i):
while (i * 2) <= self.currentSize:
mc = self.minChild(i) # 找出较小的子节点
if self.heapList[i] > 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.hepList[i * 2 + 1]:
return i * 2
else:
return i * 2 + 1
def delMin(self):
retval = self.heapList[1] # 移走堆顶
self.heapList[1] = self.heapList[self.currenSize]
self.currentSize -= 1
self.heapList.pop()
self.percDown(1) # 新顶下沉
return retval
buildHeap
方法,从无序表生成“堆”。
我们最自然的想法是:用insert(key)
方法,将无序表中的数据项逐个insert到堆中,但这么做的总代价是
O
(
n
log
n
)
O(n\log n)
O(nlogn)。其实,用“下沉”法,能够将总代价控制在
O
(
n
)
O(n)
O(n)。
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 -= 1
print(self.heapList, i)
le i > 0:
print(self.heapList, i)
self.percDown(i)
i -= 1
print(self.heapList, i)