之前写过一个有序/无序列表来实现优先级队列的,在这里。
这次,我们尝试一下用堆来实现。
堆
首先的话,这里默认的堆是小顶堆。
堆,是二叉树的一种,更准确来说是完全二叉树,所以经常使用数组(python中用列表)来存储。如果对这一部分有问题,建议看这里。
堆的性质就是根部的结点最小(这里默认小顶堆),因为树的递归性质,对于每一个子树都成立。
堆支持插入和删除,一般删除都是指删除根节点,并且认为在操作之前堆是满足性质的。
插入
我们习惯的是先在列表的最后进行插入,然后此时一般会破坏堆的性质,我们就需要进行整理。整理的方法也很容易想到,破坏了也就是插入的叶子节点小于其根节点。
插入的一定是叶子,这个不用说了。
不过看一下下面这种情况:
在进行了一次的交换后,1成了4和5的根节点,但此时仍然不是一个堆,因为3>1,所以我们还需要沿着向根节点的路径一直走下去。
删除
因为删除是默认删除根节点,也就是pop出最小的元素,所以不能直接删除,我们将列表最后一个元素提到根节点,很明显这也是一个破坏了堆结构的行为,所以我们还需要整理。
但这次,是顶上出了问题,所以我们要自顶向下。
从上往下选就有问题了,一般都是有两个孩子准备"上位"的,我们就需要选一个小一点的孩子。
同样,只处理一处是不合适的,所以我们还是需要沿着较小孩子的路径向下走。
初始化
如果是一个从最开始就这样插入删除的,那一定是堆。但就怕半路上要整一个堆,就比较烦。
首先,我们来达成几个共识:
- 对于一个无序序列要整理成堆,我们应当采用的是自顶向下的整理方式
- 如果是使用自顶向下的排序方式,我们只需要处理非叶子结点
- 如果是自顶向下,我们需要从最后一个非叶子结点开始
首先,自顶向下的方式,本身就是通过给定一个子树的根节点,一直往下直到叶子结点,所以是不要处理叶子结点的;
另外,自顶向下本身也就是一条路径,如果是破坏能实现构成堆,但如果是无序的,如果从根节点开始,我们可以看一下这个:
第一次,将7-3对换,然后走到3位置上(现在为7),然后2-7对换,此时根节点已经结束了,2就没有机会到根节点了。
但是,如果我们采用的是从最后一个非叶子结点,首先是一定能将这个子树整理成堆的(撑死就两个孩子,一定可以的),然后上面一层都的左右孩子都是堆,调用自顶向下就能实现整理了。
细心的可以发现,其实我们在删除的时候,左右子树就都是堆,所以成立。
然后,我们讨论一下自底向上的方式。
我们当时是在最后插入一个结点的情况下,使用了该方法,所以如果处理需要从第一个结点开始,并且因为从底下开始的性质,是一定要到最后一个结点。
那么也就相当于是从空堆开始,每次插入一个……,那tm当然能成堆了。
不过,要知道叶子节点数=非叶子结点数(+1),所以这个方法直接把难度拉大了一倍。
实现
有了上面的描述,这部分应该是不难的。
用到了之前的一个基类,也贴出来了。
class PriorityQueueBase():
"""Abstract base class for a priority queue"""
class _Item():
"""lightweight composite to store priority queue items"""
__slots__ = '_key', '_value'
def __init__(self,k,v):
self._key = k
self._value = v
def