算法导论 — 6.4 堆排序算法

笔记

堆排序算法一开始调用BUILD-MAX-HEAP将输入数组 A [ 1.. n ] A[1..n] 构建成最大堆。此时,数组中的最大元素在根结点 A [ 1 ] A[1] ,因此通过将 A [ 1 ] A[1] A [ n ] A[n] 交换,可以将 A [ 1 ] A[1] 放置在排序后的正确位置。由于根结点换了一个新的元素,现在的根结点有可能不满足堆的性质,因此需要调用MAX-HEAPIFY来维护堆的性质,只不过MAX-HEAPIFY是针对 A [ 1.. n 1 ] A[1..n-1] 来调用的。然后堆排序算法重复这一过程,直到堆的大小一直降到 1 1 为止。
  在这里插入图片描述
  堆排序的时间复杂度为 O ( n l g n ) O(n{\rm lg}n) 。因为一共调用了 n 1 n-1 次MAX-HEAPIFY,每次调用的时间为 O ( l g n ) O({\rm lg}n) ;而只调用了一次BUILD-MAX-HEAP,调用时间为 O ( n ) O(n)
  下图给出了一个堆排序的例子。
  在这里插入图片描述

练习

6.4-1 参照图6-4的方法,说明HEAPSORT在数组A = <5, 13, 2, 25, 7, 17, 20, 8, 4>上的操作过程。
  
  在这里插入图片描述

6.4-2 试分析在使用下列循环不变式时,HEAPSORT的正确性:
  在算法的第2~5行for循环每次迭代开始时,子数组 A [ 1.. i ] A[1..i] 是一个包含了数组 A [ 1.. n ] A[1..n] 中最小的 i i 个元素的最大堆,而子数组 A [ i + 1.. n ] A[i+1..n] 包含了数组 A [ 1.. n ] A[1..n] 中已排好序的最大的 n i n-i 个元素。
  
  (1) 初始状态
  此时 i = n i = n ,子数组 A [ 1.. i ] A[1..i] 是一个包含了所有元素并且已经建好了的最大堆,而子数组 A [ i + 1.. n ] A[i+1..n] 为空。因此,在进入迭代之前,循环不变式为真。
  (2) 保持
  假设在第 i i 次迭代之前,循环不变式为真。那么此时子数组 A [ 1.. i ] A[1..i] 包含了整个数组 A [ 1.. n ] A[1..n] 中最小的 i i 个元素,并且 A [ 1.. i ] A[1..i] 是一个最大堆,因此 A [ 1 ] A[1] 保存了子数组 A [ 1.. i ] A[1..i] 中的最大元素,也就是整个数组 A [ 1.. n ] A[1..n] 中第 n i + 1 n-i+1 大的元素(有 n i n-i 个元素比它大,所以按从大到小顺序,它是第 n i + 1 n-i+1 个)。
  迭代过程的第一步是将这个第 n i + 1 n-i+1 大的元素从 A [ 1.. i ] A[1..i] 中取出,并与最大堆 A [ 1.. i ] A[1..i] 的末尾元素 A [ i ] A[i] 交换。此时 A [ i ] A[i] 变成了第 n i + 1 n-i+1 大的元素,它与 A [ i + 1.. n ] A[i+1..n] 组成了一个子数组 A [ i . . n ] A[i..n] ,并且 A [ i . . n ] A[i..n] 包含了已排好序的最大的 n i + 1 = n ( i 1 ) n-i+1 = n-(i-1) 个元素。因此进入下一次迭代 ( i 1 ) (i-1) 之前,循环不变式的后半部分为真。
  上一步将最大堆 A [ 1.. i ] A[1..i] 中最大元素取出了,并且将堆的末尾元素交换到了 A [ 1 ] A[1] 的位置,因此堆的大小减小了 1 1 ,并且 A [ 1 ] A[1] 位置有可能不满足最大堆的性质了。因此,迭代过程的第二步是将 h e a p _ s i z e heap\_size 1 1 ,并调用MAX-HEAPIFY(A, 1)来维持最大堆的性质。注意,此时最大堆已经变成了 A [ 1.. i 1 ] A[1..i-1] ,并且它包含了整个数组 A [ 1.. n ] A[1..n] 中最小的 i 1 i-1 个元素。因此进入下一次迭代 ( i 1 ) (i-1) 之前,循环不变式的前半部分也为真。
  (3) 终止
  前面说明了在每次迭代之前,循环不变式都为真。循环终止时,有 i = 1 i = 1 ,此时循环不变式也应当为真。将 i = 1 i = 1 代入循环不变式,得到“ A [ 1 ] A[1] 实际上是整个数组 A [ 1.. n ] A[1..n] 中的最小元素,子数组 A [ 2.. n ] A[2..n] 包含了整个数组 A [ 1.. n ] A[1..n] 中已排好序的最大的 n 1 n-1 个元素”。显然, A [ 1 ] A[1] 已经是最小的元素,并且 A [ 1 ] A[1] 之后的元素已经排好序,所以整个数组 A [ 1.. n ] A[1..n] 都已经排好序。

6.4-3 对于一个按升序排列的包含 n n 个元素的有序数组 A A 来说,HEAPSORT的时间复杂度是多少?如果 A A 是降序呢?
  
  我们要假设数组中的元素各不相同。无论数组按升序排列还是按降序排序,HEAPSORT的时间复杂度都为 O ( n l g n ) O(n{\rm lg}n)
  如果数组中的元素全部相同,那么HEAPSORT的时间复杂度降为 O ( n ) O(n) 。因为此时for循环中的MAX-HEAPIFY的时间复杂度都为O(1)。

6.4-4 证明:在最坏情况下,HEAPSORT的时间复杂度是 Ω ( n l g n ) Ω(n{\rm lg}n)
  
  由于建堆的时间属于低阶项,我们这里不考虑建堆的时间,只考察建堆后的排序时间。每次迭代交换 A [ 1 ] A[1] A [ i ] A[i] 之后,此时堆中一共有 i 1 i-1 个元素,并且此时堆的高度为 l g ( i 1 ) ⌊{\rm lg}⁡(i-1)⌋ A [ i ] A[i] 被交换到 A [ 1 ] A[1] 的位置后,再调用MAX-HEAPIFY来维护堆的性质。最坏情况下 A [ i ] A[i] 会被逐层下降到最底层为止,下降的次数等于此时堆的高度 l g ( i 1 ) ⌊{\rm lg}⁡(i-1)⌋ 。因此,最坏情况下,HEAPSORT调用MAX-HEAPIFY的总的开销为
   i = 2 n l g ( i 1 ) i = 2 n ( l g ( i 1 ) 1 ) = i = 2 n l g ( i 1 ) ( n 1 ) = l g ( ( n 1 ) ! ) ( n 1 ) = Θ ( n l g n ) \sum_{i=2}^n⌊{\rm lg}(i-1)⌋ ≥\sum_{i=2}^n({\rm lg}⁡(i-1)-1) =\sum_{i=2}^n{\rm lg}⁡(i-1)-(n-1)={\rm lg}((n-1)!)-(n-1)=Θ(n{\rm lg}n)
  这里用到了第3章的结论 l g ( n ! ) = Θ ( n l g n ) {\rm lg}(n!) = Θ(n{\rm lg}n) 。由此可见,最坏情况下,HEAPSORT的时间复杂度是 Ω ( n l g n ) Ω(n{\rm lg}n)

6.4-5 证明:在所有元素都不同的情况下,HEAPSORT的时间复杂度是 Ω ( n l g n ) Ω(n{\rm lg}n)
  上题证明了堆排序的最坏情况时间复杂度为 Ω ( n l g n ) Ω(n{\rm lg}n) 。本题显然还需要证明最好情况下的时间复杂度也为 Ω ( n l g n ) Ω(n{\rm lg}n) 。不过这一证明过程较为复杂,我们记住结论即可。

以下是堆排序的代码链接。
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter06/HeapSort

猜你喜欢

转载自blog.csdn.net/yangtzhou/article/details/84781341