堆排序
堆排序不同于归并排序的是,堆排序具有和插入排序一样的空间原址性,任何时候都只需要常数个额外的元素空间存储临时数据。因此,堆排序是集合了插入排序和归并排序两种算法优点的排序方法。
6.1 堆
(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组中的一个元素,除了最底层,该树是完全充满的,而且是从左到右填充。给定下面三个函数(这里计算左右孩子的方式说明数组是从1开始编号的,即A[1, A.heap_size],如果是A[0, A.heap_size-1]的话,则左右孩子的索引应该是2*i+1, 2*i+2),在堆排序的好的实现中,这三个函数通常是以“宏”或者“内联函数”的方式实现的:
PARENT(i)
return i/2
LEFT(i)
return 2*i
RIGHT(i)
return 2*i+1
二叉堆有两种形式,最大堆和最小堆。两个堆都是通过父子元素的关系确定的。对于这两种形式,我们能且仅能确定的是根元素分别是数组中的最大值和最小值,这也是堆排序的基础。
一个堆中结点的高度是该结点到叶结点最长简单路径上边的数目。下面几个是堆排序中的基本过程。
- MAX-HEAPITY: 其时间复杂度为O(lgn), 它是维护最大堆性质的关键。
- BUILD-MAX-HEAP: 具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆。
- HEAPSORT: 其时间复杂度为O(nlgn),功能是对一个数组进行原址排序。
- MAX-HEAP-INSERT/HEAP-EXTRACT-MAX/HEAP-INCREASE-KEY/HEAP-MAXIMUM: 时间复杂度为O(nlgn),功能是利用堆实现一个优先队列。
6.2 维护堆的性质
MAX-HEAPITY(A, i)
l=LEFT(i)
r=RIGHT(i)
if l<=A.heap_size and A[l]>A[i]
largest=l
else
largest=i
if r<=A.heap_size and A[r]>A[i]
largest=r
if largest!=i
exchange A[i] with A[largest]
MAX-HEAPITY(A, largest)
6.3 建堆
我们可以用自底向上的方法利用过程MAX-HEAPITY把一个大小为n=A.length的数组A[1, n]转换为最大堆。
BUILD-MAX-HEAP(A)
A.heap_size=A.length
for i=A.length/2 downto 1
MAX-HEAPITY(A, i)
这里的循环不变量是:在第2~3行中每一个for循环开始的地方,结点i+1, i+2, …, n都是一个最大堆的根结点。
6.4 堆排序算法
HEAPSORT(A)
BUILD-MAX-HEAP(A)
for i=A.length downto 2
exchange A[i] with A[1]
A.heap_size=A.heap_size-1
MAX-HEAPITY(A, 1)
6.5 优先队列
优先队列(priority queue)是一种用来维护由一个元素构成的集合S的数据结构,其中的每一个元素都有一个相关的值,成为关键字(key),一个最大优先队列支持以下操作:
- INSERT(S, x): 把元素x插入集合S中。
- MAXIMUM(S): 返回S中具有最大关键字的元素。
- EXTRACT-MAX(S): 去掉并返回S中的具有最大关键字的元素。
INCREASE-KEY(S, x, k): 将元素x的关键字值增加到k,这里假设k的值不小于x的原关键字值。
MAXIMUM(A) return A[1] 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-HEAPITY(A, 1) return max HEAP-INCREASE-KEY(A, i, key) if key<A[i] error "new key is smaller than current key" A[i]=key while i>1 and A[PARENT(i)]<A[i] exchange A[i] with A[PARENT(i)] i=PARENT(i) MAX-HEAP-INSERT(A, key) A.heap_size=A.heap_size+1 A[A.heap_size]=-inf HEAP_INCREASE_KEY(A, A.heap_size, key)