堆排序(heapsort) 像合并排序而不像插入顺序,堆排序的运行时间为O(nlgn) 。像插入排序而不像合并排序,它是一种原地( in place) 排序算法:在任何时候,数组中只有常数个元素存储在输入数组以外。
堆:
(二叉)堆数据结构是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层可能除外(最后一层从一个结点的左子树开始填) 。
堆存在以下性质:
根节点:A[1]
父结点:Parent[i] = i/2
左子节点:Left[i] = 2i
右子节点:Right[i] = 2i + 1
数组长度为:A.length
数组中有效数据长度为:A.heap-size
堆分为最大堆和最小堆,在最大堆中 Parent[i] >= A[i],在最小堆中 Parent[i] <= A[i]
表示堆的数组A是一个具有两个属性的对象:length[A]是数组中的元素个数, heap-size[A]是存放在A中的堆的元素个数。即虽然A[1. .length[A]]中都可以包含有效值,但A[heap-size [A]]之后的元素都不属于相应的堆,。
二叉堆有两种:最大堆和最小堆(小根堆)。在堆排序算法中,我们使用的是最大堆。最小堆通常在构造优先队列时使用。
下面几个是堆排序中的基本过程:
MAX-HEAPITY: 其时间复杂度为O(lgn), 它是维护最大堆性质的关键。
BUILD-MAX-HEAP: 具有线性时间复杂度,功能是从无序的输入数据数组中构造一个最大堆。
HEAPSORT: 其时间复杂度为O(nlgn),功能是对一个数组进行原址排序。
MAX-HEAP-INSERT/HEAP-EXTRACT-MAX/HEAP-INCREASE-KEY/HEAP-MAXIMUM: 时间复杂度为O(nlgn),功能是利用堆实现一个优先队列。
维护堆的性质:
MAX-HEAPIFY 是对最大堆进行操作的重要的子程序。调节父结点与子结点位置,使他们满足最大堆的大小关系。将A[i]的值在最大堆中“逐级下降”,从而使以下标i为根结点的子树重新遵循最大堆的性质。时间复杂度为O(lgn)。
伪代码:
MAX-HEAPIFY(A,i)
l <- Left[i]
r <- Right[i]
if l <= heap-size[A] and A[l] > A[i]
then largest <- l
else largest <- i
if r <= heap-size[A] and A[r] > A[lagest]
then lagest <- r
if i != largest //当根结点不是最大值时
then exchage A[i] <-> A[lagest]
MAX-HEAPIFY(A,lagest)
//交换位置后,以该结点为根的子树可能违法最大堆的性质,所以需要递归调用
如:
下图描述了MAX- HEAPIFY的过程。在算法的每一步里,从元素A[i], A[LEFT(i)] 和A[RIGHT(i)] 中找出最大的,并将其下标存在largest 中。如果A[i]是最大的,则以i为根的子树己是最大堆,程序结束。否则,i的某个子结点中有最大元素,则交换A[i]和A[largest] , 从而使i及其子女满足堆性质。下标为largest 的结点在交换后的值是A[i] , 以该结点为根的子树又有可能违反最大堆性质。因而要对该子树递归调用MAX- HEAPIFY。
建堆:
建堆(BUILD-MAX-HEAP):将无序的数组转换为最大堆。
伪代码:
BUILD-MAX-HEAP(A)
heap-size[A] <- length[A]
for i <- [length[A]/2] downto 1
//此处i的值为length[A]/2向下取整的值,因为A[length[A]/2+1...length[A]]为叶子结点,没有子结点,所以只需考虑前半部分的值
do MAX-HEAPIFY(A,i)
过程BUILD-MAX-HEAP对树中的每一个其他结点都调用一次MAX-HEAPIFY。时间复杂度为 O(n)。
如:
堆排序算法:
堆排序算法(HEAPSORT):建堆后,数组中的数据并不是有序的,但最大值一定位于根结点A[1],根据此性质,可以进行排序。时间复杂度为O(nlogn)。
伪代码:
HEAPSORT(A)
BUILD-MAX-HEAP(A)
for i <- length[A] downto 2
//不断将最大值放在堆中最后一个元素位置,对应就是数组中由后向前进行排序
do exchange A[1] <-> A[i]
//将最大元素置后
heap-size[A] <- heap-size[A] - 1
//有效长度减1,表示该元素已经归位
MAX-HEAPIFY(A,1)
//因为将原堆末尾元素提到了根结点,所以需要对根结点重新执行MAX-HEAPIFY维护堆的性质。
如:
优先级队列:
优先队列(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的原关键字值。
伪代码:
HEAP-MAXIMUM(A)
return A[1]
HEAD-EXTRACT-MAX(A)
if heap_size[A]<1
then error"heap underflow"
max <- A[1]
A[1] <- A[heap_size[A]]
heap_size[A] <- heap_size[A]-1
MAX-HEAPIFY(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]
do exchange A[i] <-> A[PARENT(i)]
i <- PARENT(i)
MAX-HEAP-INSERT(A, key)
heap_size[A] <- heap_size[A]+1
A[heap_size[A]] <- -inf
HEAP_INCREASE_KEY(A, heap_size[A], key)
程序HEAP-MAXIMUM 用O(1) 时间实现了MAXIMUM 操作。HEAP-EXTRACT-MAX 的运行时间为O(lgn), 因为它除了时间代价为O(lgn) 的MAXHEAPIFY外,只有很少的固定量的工作。
程序HEAP-INCREASE-KEY实现了INCREASE-KEY 操作。在优先级队列中,关键字值需要增加的元素由对应数组的下标i来标识。该过程首先将元素A[i] 的关键字值更新为新的值。由于增大A[i] 的关键字可能会违反最大堆性质,所以过程中采用了类似于INSERTION-SORT的插入循环(第5 -7 行)的方式,在从本结点往根结点移动的路径上,为新增大的关键字寻找合适的位置。在移动的过程中,此元素不断地与其父母相比,如果此元素的关键字较大,则交换它们的关键字且继续移动。当元素的关键字小于其父母时,此时最大堆性质成立,因此程序终止。
MAX-HEAP-INSERT 实现了INSERT 操作。它的输入是要插入到最大堆A中的新元素的关键字。这个程序首先加入一个关键字值为负无穷的叶结点来扩展最大堆,然后调用HEAP-INCREASE- KEY来设置新结点的关键字的正确值,并保持最大堆性质。在包含n个元素的堆上, MAX-HEAP-INSERT 的运行时间为O(lgn) 。
如:
堆排序的代码实现:
C/C++代码如下: