13.4 堆
堆(heap)是一个完全二叉树,其结点组织是基于各自结点的数据域。它有两个重要的变种——最大堆和最小堆。
最大堆(max-heap),具有称为堆序性质(heap order property)。对于其任意一个内结点而言,该结点处的值均大于其两个子结点的值。
最小堆(min-heap),则具有相反的堆序性质,对于其任意一个内结点而言,该结点处的值均小于其两个子结点的值。
13.4.1 定义
插入操作
向堆中插入新值,堆序性质和堆形性质(heap shape property,即保持其作为完全二叉树的性质)都要保持。
例如分别向如下两个最大堆插入元素90和元素41。
我们从堆的底部开始,先创建新结点,再将之添加到堆中,再与其父结点比较,若大于父结点,则两者互换,重复此过程,直至小于父结点为止。向上移动的过程就称为sift-up过程。
提取
从堆中提取元素,即从堆中删除元素。通常,我们是从最大堆中提取最大的元素,从最小堆中提取最小的元素。例如从如下最大堆中提取最大值。
首先是复制根结点,其次是最底层最右边的元素,复制到根结点中,然后与其子结点比较,若小于子结点,则将其与较大的子结点互换,重复此过程,直至其到达叶结点或者碰到比其更小的结点为止。向下移动的过程称为sift-down。
13.4.2 实现
虽然堆本质是二叉树,但是几乎不用动态链式结构来实现,其原因是堆涉及到的向上移动和向下移动,即sift-up和sift-down过程。我们使用数组或者动态数组(python列表)来实现堆。
访问结点
由于堆是完全二叉树,其中不会有空洞致使内结点缺失。因此可以将根结点放入数组中的索引为0的位置,其两个子结点则分别放入索引为1和2的位置,依次类推。则对于数组中索引为i的位置储存的结点,有
父结点的索引为 parent = (i - 1) / 2
左子结点的索引为 left = 2 * i + 1
右子结点的索引为right = 2 * i + 2
判读一个结点是否有子结点,则可计算其假设左子结点和右子结点的索引值,若没有超出数组容量,则相应的子结点存在,若超出数组容量,则相应的子结点就不存在。
#-*-coding: utf-8-*-
# 用数组实现最大堆
class MaxHeap(object):
# 创建最大堆,使用最大容量为maxSize的数组
def __init__(self, maxSize):
self._elements = Array(maxSize)
self._count = 0 # 当前最大堆中的结点个数
# 最大堆中的结点数目
def __len__(self):
return self._count
# 最大堆的容量
def capacity(self):
return len(self._elements)
# 向最大堆中添加元素
def add(self, value):
assert self._count < self.capacity(), "Cannot add to a full heap."
self._elements[self._count] = value
self._count += 1
self._siftUp(self._count-1) # sift-up过程
# 从最大堆中提取最大元素,即根结点
def extract(self):
assert self._count > 0, "Cannot extract from an empty heap."
value = self._elements[0]
self._count -= 1
self._elements[0] = self._elements[self._count]
self._siftDown(0) # sift-down过程
# sift-up过程
def _siftUp(self, ndx):
if ndx > 0:
parent = ndx / 2
if self._elements[ndx] > self._elements[parent]:
self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]
self._siftUp(parent)
# sift-down过程
def _siftDown(self, ndx):
left = 2 * ndx + 1
right = 2 * ndx + 2
# 找出两子结点中的较大者
largest = ndx
if left < count and self._elements[left] >= self._elements[largest]:
largest = left
elif right < count and self._elements[right] >= self._elements[largest]:
largest = right
# 如果ndx结点的值小于两子结点中的较大者,即largest,则将两者互换
if largest != ndx:
self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]
self._siftDown(largest)
实现 | 最坏情况 | 最坏情况 | 均摊成本 | 均摊成本 |
入队 | 出队 | 入队 | 出队 | |
python列表 | O(n) | O(n) | O(1) | O(n) |
链表 | O(1) | O(n) | - | - |
最小堆(数组) | O(log n) | O(log n) | - | - |
最小堆(python列表) | O(n) | O(n) | O(log n) | O(log n) |