堆排序

来源:互联网 发布:怎么解决淘宝自主访问 编辑:程序博客网 时间:2024/06/06 16:29

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 = lelse largest = iif r<= A.heap_size && A[r] > A[largest]    largest = rif 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.lengthfor 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 - 1MAX_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] = keywhile 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 + 1A[heap_size] = -∞HEAP_INCREASE_KEY(A, A.heap_size, key)
0 0