目录
一、优先队列
队列有一种变体称为“优先队列”,优先队列的出队跟队列一样从队首出队,但在优先队列内部, 数据项的次序却是由“优先级”来确定:高优先级的数据项排在队首,而低优先级的数据项则排在后面。这样,优先队列的入队操作就比较复杂,需要将数据项根据其优先级尽量挤到队列前方。
有什么方案可以用来实现优先队列?
1.1 二叉堆Binary Heap实现优先队列
二叉堆能够将优先队列的入队和出队复杂度都保持在 O ( l o g n ) O(log n) O(logn)。二叉堆的有趣之处在于, 其逻辑结构上像二叉树, 却是用非嵌套的列表来实现的
- 最小key排在队首的称为“最小堆min heap”
- 最大key排在队首的是“最大堆max heap”。
ADT BinaryHeap的操作定义如下:
函数 | 含义 |
---|---|
BinaryHeap() | 创建一个空二叉堆对象 |
insert(k) | 将新key加入到堆中 |
findMin() | 返回堆中的最小项,最小项仍保留在堆中 |
delMin() | 返回堆中的最小项,同时从堆中删除 |
isEmpty() | 返回堆是否为空 |
size() | 返回堆中key的个数 |
buildHeap(list) | 从一个key列表创建新堆 |
【代码】:
from pythonds.trees.binheap import BinHeap
bh = BinHeap()
bh.insert(5)
bh.insert(7)
bh.insert(3)
bh.insert(11)
print(bh.delMin())
print(bh.delMin())
print(bh.delMin())
print(bh.delMin())
为了使堆操作能保持在对数水平上, 就必须采用二叉树结构;
同样, 如果要使操作始终保持在对数数量级上, 就必须始终保持二叉树的“平衡”
– 树根左右子树拥有相同数量的节点
我们采用“完全二叉树”的结构来近似实现“平衡”
1.2 完全二叉树
叶节点最多只出现在最底层和次底层,而且最底层的叶节点都连续集中在最左边,每个内部节点都有两个子节点, 最多只有1个节点例外,比如下图中的18号节点。
如果节点的下标为p,那么其左子节点下标为2p,右子节点为2p+1,其父节点下标为p//2任何一个节点x, 其父节点p中的key均小于x中的key,这样,符合“堆”性质的二叉树,其中任何一条路径,均是一个已排序数列, 根节点的key最小。
二、二叉堆的实现
堆的性质:在任何一条路径上,它都是一个有序的队列。
2.1 初始化
采用一个列表来保存堆数据,其中表首下标为0的项无用,但为了后面代码可以用到简单的整数乘除法,仍保留它。
class Binheap:
def __init__(self):
self.heapList = [0]
self.currentSize = 0
insert(key)方法
- 首先,为了保持“完全二叉树”的性质,新key应该添加到列表末尾。会有问题吗?
新key加在列表末尾,显然无法保持“堆”次序,即路径上的顺序被破坏。虽然对其它路径的次序没有影响,但对于其到根的路径可能破坏次序。
需要将新key沿着路径来“上浮”到其正确位置。
注意:新key的“上浮”不会影响其它路径节点的“堆”次序
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 #size+1
self.percUp(self.currentSize) # 上浮,比较大小
delMin()方法:移走整个堆中最小的key
移走根节点heapList[1],并保证堆次序不被破坏。
先用最后一个节点来代替根节点,再将新的根节点沿着一条路径“下沉”,直到比两个子节点都小。
“下沉”路径的选择:如果比子节点大,那么选择两个子节点中较小的子节点交换下沉。示 意图如下:
- 左子节点9,右子节点 11,27和9交换。
- 左子节点14,右子节点 18,27和14交换。
- 左子节点33,右子节点 17,27和17交换。
【代码】:
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(lst)方法:从无序表生成“堆”
下沉法,能够将总代价控制在
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)