1. 堆
堆是一个数组,又近似于一个完全二叉树。
如图,树上的每一个结点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充的。
树的根节点是A[1],给定一个下标i,我们很容易得到它的父节点,左右孩子结点的下标。
PARENT(i)
return i / 2
LEFT(i)
return 2 * i
RIGHT(i)
return 2 * i + 1
(在计算机中,通过将i 的值左移一位,可以计算LEFT,通过将i 的值左移一位并加1,可以快速得到RIGHT。同理,可以通过将i 的值右移一位得到PARENT。且这三个函数可通过“宏”或“内联函数”实现)
二叉堆可以分为两种形式:最大堆和最小堆。
在最大堆中,除了根节点以外所有的结点i 都要满足:A[PARENT] >= A[i]。
同理,最小堆中,除了根节点以外的所有结点都要满足:A[PARENT] <= A[i]。
PS:
堆的性质有:
- 含n个元素的堆的高度为lgn。
- 当用数组表示存储n个元素的堆时,叶结点的下标分别是n/2+1, n/2+2, … , n。
2. 堆排序–实现
2.1 维护堆的性质
假定根节点为LEFT(i)和RIGHT(i)的二叉树都是最大堆。
输入一个数组A和下标i ,使得A[i]的值在最大堆中“逐级下降”(即A[i]为堆中最大值),从而使得以下标i 为根节点的子树重新遵循最大堆的性质。
通过将当前结点i 与左右子结点对比,找到最大值,放在i 的位置(根节点),并递归调用MAX_HEAPIFY从而维护最大堆性质。
伪代码:
FUNCTION MAX_HEAPIFY(A, i)
l = LEFT(i);
r = RIGHT(i);
if l<= A.heap_size && A[l] > A[i]
largest = l
else largest = i
if r<= A.heap_size && A[r] > A[largest]
largest = r
if largest != i
exchange A[i] with A[largest]
MAX_HEAPIFY(A, largest)
对于一个树高为h的结点来说,MAX_HEAPIFY的时间复杂度为O(h)。
2.2 构建最大堆
我们用自底向上的方法利用MAX_HEAPIFY把一个大小为A.heap_size的数组转换为A[1,…,n]转换为最大堆。
由堆的性质可知,子数组A[n/2 + 1….n] 中的元素都是树的叶节点。每个叶结点都可以看成是只包含一个元素的堆。
- 初始化:在for循环之前,可确定A[n/2 + 1], … , A[n]中的元素都是叶结点,因此看做是最大堆的根节点。
- 保持:利用MAX_HEAPIFY不断将结点i 添加至堆中,并维持最大堆的性质。
- 终止:当i = 0时,因为MAX_HEAPIFY不断维护最大堆性质,使得每个结点都是一个最大堆的根。
伪代码:
FUNCTION BUILD_MAX_HEAP(A)
A.heap_size = A.length
for i = A.length / 2 downto 1
MAX_HEAPIFY(A, i)
由此,每次调用MAX_HEAPIFY的时间复杂度为O(lgn),而BUILD_MAX_HEAP需要O(n)次这样的调用,总的时间复杂度为O(n · lgn)。但这个上界并不是渐近紧确的。
因为不同的MAX_HEAPIFY的时间与当前结点的高度有关,最后计算所得,BUILD_MAX_HEAP的时间复杂度为O(n)。
2.3堆排序
因为数组中的最大元素总在根节点A[1]中,通过把它与A[n]进行互换,我们可以将最大值放在最后,从而进行由小到大的排序。这时,我们从堆中去掉结点n(因为它已经在正确的位置,通过减少A.heap_size的值来实现),剩余的结点中,原来的根的孩子结点仍是最大堆,但新的根节点可能会违背最大堆的性质,这时需要调用MAX_HEAPIFY(A,1).
FUNCTION HEAP_SORT(A)
BUILD_MAX_HEAP(A)
for i = A.length downto 2
exchange A[1] with A[i]
A.heap_size -= 1
MAX_HEAPIFY(A, 1)
HEAP_SORT的时间复杂度为O(n · lgn)。
3. 优先队列
优先队列是一种用来维护由一组元素构成的集合S的数据结构。一个优先队列支持以下操作:
- INSERT(S, x):把元素x插入集合S中。
- MAXIMUM(S):返回S中具有最大关键字的元素。
- EXTRACT_MAX(S):去掉并返回S中的具有最大关键字的元素。
- INCREASE_KEY(S, x, k):将元素x的关键字值增加到k,假设k值不小于x的原值。
最大优先队列的应用很多,其中一个就是作业调度,调度一个优先级最大的作业执行。
MAXIMUM伪代码: 时间复杂度为O(1)。
FUNCTION HEAP_MAXIMUM(A)
return A[1]
EXTRACT_MAX伪代码: 时间复杂度为O(lgn)。
FUNCTION HEAP_EXTRACT_MAX(A)
if A.heap_size < 1
error"heap underflow"
max = A[1]
A[1] = A[A.heap_size]
A.heap_size = A.heap_size - 1
MAX_HEAPIFY(A, 1)
return max
INCREASE_KEY伪代码: 时间复杂度为O(lgn)。
FUNCTION HEAP_INCREASE_KEY(A, i, key)
if key < A[i]
error"new key is smaller than current key"
A[i] = key
while i > 1 && A[PARENT(i)] < A[i]
exchange A[i] with A[PARENT(i)]
i = PARENT(i)
INSERT伪代码: 时间复杂度为O(lgn)。
FUNCTION HEAP_INSERT(A, key)
A.heap_size = A.heap_size + 1
A[heap_size] = -∞
HEAP_INCREASE_KEY(A, A.heap_size, key)