【算法导论】第6章 堆排序 (1)


Introduction to Algorithms - Third Edition
Part II. Sorting and Order Statistics
Chapter 6. Heapsort


堆排序是原地排序算法,其运行时间是 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)

堆排序引入了一种算法设计技术:使用一种数据结构管理信息,这种情况下的数据结构是“堆”。
堆数据结构还可以建构一个有效的优先级队列。

“堆”一词最初是在堆排序中创造的,但后来指“垃圾收集存储区”,如编程语言 Java 和 Lisp 所提供的。
在本书中提到的堆,都指的是堆数据结构,不是垃圾收集存储区。


6.1 堆

(二叉)堆数据结构是一个数组对象,可以将其视为近似完全二叉树。树的每个节点对应于数组的一个元素。该树完全填充了所有层次,除了最后一层(从左填充到某点)。
堆数据结构
表示堆的数组 A A A 具有两个属性: A . l e n g t h A.length A.length 通常提供数组中的元素个数, A . h e a p - s i z e A.heap\text{-}size A.heap-size 表示堆中多少个元素存储在数组 A A A 中。
尽管 A [ 1.. A . l e n g t h ] A[1..A.length] A[1..A.length] 可以包含数字,但是只有 A [ 1.. A . h e a p - s i z e ] A[1..A.heap\text{-}size] A[1..A.heap-size] 中的元素才是堆中的有效元素,其中 0 ≤ A . h e a p - s i z e ≤ A . l e n g t h 0 \le A.heap\text{-}size \le A.length 0A.heap-sizeA.length
树的根是 A [ 1 ] A[1] A[1],给定一个节点的索引 i i i,可以轻松地计算其父节点,左子节点和右子节点的索引:

PARENT ( i ) \texttt{PARENT} (i) PARENT(i)
return ⌊ i / 2 ⌋ \lfloor i / 2 \rfloor i/2
LEFT ( i ) \texttt{LEFT} (i) LEFT(i)
return 2 i 2i 2i
RIGHT ( i ) \texttt{RIGHT} (i) RIGHT(i)
return 2 i + 1 2i+1 2i+1

在大多数计算机中,通过简单地将 i i i 的二进制表示左移一位, L E F T \tt LEFT LEFT 过程可在一条指令内计算 2 i 2i 2i
R I G H T \tt RIGHT RIGHT 过程可通过将 i i i 的二进制表示左移一位,然后再添加 1 作为低阶位来快速计算 2 i + 1 2i+1 2i+1
P A R E N T \tt PARENT PARENT 过程可以通过将 i i i 右移一位来计算 ⌊ i / 2 ⌋ \lfloor i / 2 \rfloor i/2
好的堆排序的实现,通常将这些过程作为“宏”过程或“内联”过程来实现。

二叉堆有两种:最大堆(max-heap)和最小堆(min-heap)。

  • 在最大堆中,最大堆性质是,除了根以外的每个结点 i i i,有 A [ PARENT ( i ) ] ≥ A [ i ] A[\texttt{PARENT}(i)] \ge A[i] A[PARENT(i)]A[i]
    最大堆中的最大元素存储在根结点中。
  • 在最小堆中,最小堆性质是,除了根以外的每个结点 i i i,有 A [ PARENT ( i ) ] ≤ A [ i ] A[\texttt{PARENT}(i)] \le A[i] A[PARENT(i)]A[i]
    最小堆中的最小元素存储在根结点中。

堆可以视为一棵树,堆中一个结点的高度定义为,从该结点到叶子的最长简单下降路径上边的数目。定义堆的高度为其根结点的高度。

练习

6.1-1 在一个高度为 h h h 的堆中,元素的最大和最小个数是多少?
解: 最大个数: 2 0 + 2 1 + ⋯ + 2 h = ∑ i = 0 h 2 i = 2 ( h + 1 ) − 1 2^0 + 2^1 + \dots + 2^h = \sum_{i=0}^h 2^i = 2^{(h+1)} - 1 20+21++2h=i=0h2i=2(h+1)1
最小个数: 2 0 + 2 1 + ⋯ + 2 h − 1 + 1 = ∑ i = 0 h − 1 2 i + 1 = 2 h 2^0 + 2^1 + \dots + 2^{h-1} + 1 = \sum_{i=0}^{h-1}2^i + 1= 2^{h} 20+21++2h1+1=i=0h12i+1=2h

6.1-2 证明:含有 n n n 个元素的堆的高度是 ⌊ lg ⁡ n ⌋ \lfloor \lg n \rfloor lgn
证: 假设含有 n n n 个元素的堆的高度是 h h h。由 练习 6.1-1,得
2 h ≤ n ≤ 2 h + 1 − 1 < 2 h + 1 2^h \le n \le 2^{h+1}-1 < 2^{h+1} 2hn2h+11<2h+1
所以, h ≤ lg ⁡ n < h + 1 h \le \lg n < h+1 hlgn<h+1
因为 h h h 是一个整数,根据 ⌊ ⌋ \lfloor\rfloor 的定义,可得 h = ⌊ lg ⁡ n ⌋ h = \lfloor \lg n \rfloor h=lgn

6.1-3 证明:在最大堆中的任何子树中,子树的根结点包含该子树中的最大值。
证: 最大堆 A A A 中,对于每个结点 i i i,都有 A [ i ] > A [ LEFT ( i ) ] A[i] > A[\texttt{LEFT}(i)] A[i]>A[LEFT(i)] A [ i ] > A [ RIGHT ( i ) ] A[i] > A[\texttt{RIGHT}(i)] A[i]>A[RIGHT(i)]

6.1-4 在一个最大堆中,假设所有元素不同,则最小元素可能位于堆的什么位置?
解: 最大堆中所有叶子结点都有可能是最小元素所处的位置。

6.1-5 一个已排序好的数组是最小堆吗?
解: 若从小到大排序,是。

6.1-6 包含 < 23 , 17 , 14 , 6 , 13 , 10 , 1 , 5 , 7 , 12 > <23, 17, 14, 6, 13, 10, 1, 5, 7, 12> <23,17,14,6,13,10,1,5,7,12> 的数组是一个最大堆吗?
解: 不是。 A [ 4 ] = 6 A[4] = 6 A[4]=6 A [ 9 ] = 7 A[9] = 7 A[9]=7 A [ 4 ] A[4] A[4] A [ 9 ] A[9] A[9] 的父节点,但 A [ 4 ] < A [ 9 ] A[4] < A[9] A[4]<A[9],不符合最大堆性质。

23
17
14
6
13
10
1
5
7
12

6.1-7 证明:当用数组表示存储 n n n 个元素的堆时,叶子结点的下标是 ⌊ n / 2 ⌋ + 1 , ⌊ n / 2 ⌋ + 2 , … , n \lfloor n/2 \rfloor+1 , \lfloor n/2 \rfloor+2, \dots, n n/2+1,n/2+2,,n
证: 索引为 n n n 的元素的父节点的索引为 ⌊ n / 2 ⌋ \lfloor n/2 \rfloor n/2,又因为索引为 n n n 的元素的父节点是最后一个非叶子结点,所以第一个叶子结点索引为 ⌊ n / 2 ⌋ + 1 \lfloor n/2 \rfloor+1 n/2+1,第二个叶子结点索引为 ⌊ n / 2 ⌋ + 2 \lfloor n/2 \rfloor+2 n/2+2,直至最后一个结点索引为 n n n


6.2 保持堆的性质

假定以 LEFT ( i ) \texttt{LEFT}(i) LEFT(i) RIGHT ( i ) \texttt{RIGHT}(i) RIGHT(i) 为根的二叉树都是最大堆,但 A [ i ] A[i] A[i] 可能小于它的子节点的值,这违反了最大堆的性质。

MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY} (A, i) MAX-HEAPIFY(A,i)
l = LEFT ( i ) l = \texttt{LEFT}(i) l=LEFT(i)
r = RIGHT ( i ) r = \texttt{RIGHT}(i) r=RIGHT(i)
if l ≤ A . h e a p - s i z e l \le A.heap\text-size lA.heap-size and A [ l ] > A [ i ] A[l] > A[i] A[l]>A[i]
l a r g e s t = l largest = l largest=l
else l a r g e s t = i largest = i largest=i
if r ≤ A . h e a p - s i z e r \le A.heap\text-size rA.heap-size and A [ r ] > A [ l a r g e s t ] A[r] > A[largest] A[r]>A[largest]
l a r g e s t = r largest = r largest=r
if l a r g e s t ≠ i largest \ne i largest=i
 exchange A [ i ] A[i] A[i] with A [ l a r g e s t ] A[largest] A[largest]
MAX-HEAPIFY ( A , l a r g e s t ) \texttt{MAX-HEAPIFY} (A, largest) MAX-HEAPIFY(A,largest)

MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的运行时间: T ( n ) ≤ T ( 2 n / 3 ) + Θ ( 1 ) T(n) \le T(2n/3) + \Theta(1) T(n)T(2n/3)+Θ(1)
因为 i i i 结点的子树大小至多为 2 n / 3 2n/3 2n/3 —— 最坏情况发生在最底层半满的时候。
根据主定理的情况2(定理4.1),得 T ( n ) = O ( lg ⁡ n ) T(n) = O(\lg n) T(n)=O(lgn)

练习

6.2-1 以图 6.2 为例,图示出 MAX-HEAPIFY ( A , 3 ) \texttt{MAX-HEAPIFY} (A, 3) MAX-HEAPIFY(A,3) 作用于数组 A = < 27 , 17 , 3 , 16 , 13 , 10 , 1 , 5 , 7 , 12 , 4 , 8 , 9 , 0 > A = < 27, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0 > A=<27,17,3,16,13,10,1,5,7,12,4,8,9,0> 的过程。
解:
6.2-1

6.2-2 从过程 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 开始,为过程 MIN-HEAPIFY ( A , i ) \texttt{MIN-HEAPIFY}(A,i) MIN-HEAPIFY(A,i) 编写伪代码,对最小堆执行相应的操作。比较 MIN-HEAPIFY \texttt{MIN-HEAPIFY} MIN-HEAPIFY MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的运行时间。
解: MIN-HEAPIFY ( A , i ) \texttt{MIN-HEAPIFY} (A, i) MIN-HEAPIFY(A,i)
l = LEFT ( i ) l = \texttt{LEFT}(i) l=LEFT(i)
r = RIGHT ( i ) r = \texttt{RIGHT}(i) r=RIGHT(i)
if l ≤ A . h e a p - s i z e l \le A.heap\text-size lA.heap-size and A [ l ] < A [ i ] A[l] < A[i] A[l]<A[i]
s m a l l e s t = l smallest = l smallest=l
else s m a l l e s t = i smallest = i smallest=i
if r ≤ A . h e a p - s i z e r \le A.heap\text-size rA.heap-size and A [ r ] < A [ s m a l l e s t ] A[r] < A[smallest] A[r]<A[smallest]
s m a l l e s t = r smallest = r smallest=r
if s m a l l e s t ≠ i smallest \ne i smallest=i
 exchange A [ i ] A[i] A[i] with A [ s m a l l e s t ] A[smallest] A[smallest]
MIN-HEAPIFY ( A , s m a l l e s t ) \texttt{MIN-HEAPIFY} (A, smallest) MIN-HEAPIFY(A,smallest)

MIN-HEAPIFY \texttt{MIN-HEAPIFY} MIN-HEAPIFY 过程运行时间 T ( n ) = O ( lg ⁡ n ) T(n) = O(\lg n) T(n)=O(lgn)

6.2-3 A [ i ] A[i] A[i] 大于其子节点的值时,调用过程 MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY} (A,i) MAX-HEAPIFY(A,i) 有什么影响?
解: i i i 为根结点的树不作任何变动。

6.2-4 对于 i > A . h e a p - s i z e / 2 i > A.heap\text-size/2 i>A.heap-size/2,调用过程 MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY} (A,i) MAX-HEAPIFY(A,i) 有什么影响?
解: i i i 为根结点的树不作任何变动。

6.2-5 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的代码相当有效,除了最后一行的递归调用,它可能会使某些编译器产生低效的的代码。使用迭代控制结构(一个循环),而不是递归,写一个高效的 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY
解: MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY}(A, i) MAX-HEAPIFY(A,i)
while i ≤ A . h e a p - s i z e i \le A.heap\text-size iA.heap-size
l = LEFT ( i ) l = \texttt{LEFT}(i) l=LEFT(i)
r = RIGHT ( i ) r = \texttt{RIGHT}(i) r=RIGHT(i)
l a r g e s t = i largest = i largest=i
if l ≤ A . h e a p - s i z e l \le A.heap\text-size lA.heap-size and A [ l ] < A [ i ] A[l] < A[i] A[l]<A[i]
   l a r g e s t = l largest = l largest=l
if r ≤ A . h e a p - s i z e r \le A.heap\text-size rA.heap-size and A [ r ] > A [ l a r g e s t ] A[r] > A[largest] A[r]>A[largest]
   l a r g e s t = r largest = r largest=r
if l a r g e s t ≠ i largest \ne i largest=i
  exchange A [ i ] A[i] A[i] with A [ l a r g e s t ] A[largest] A[largest]
   i = l a r g e s t i = largest i=largest
else break // 跳出循环

6.2-6 证明:在大小为 n n n 的堆中, MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的最坏情况运行时间是 Ω ( lg ⁡ n ) \Omega(\lg n) Ω(lgn)。(提示:对于一个有 n n n 个结点的堆,设定结点的值,使得在从根到叶子的简单路径上,每个结点都能够递归调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY。)
证: 如果根上放置的值小于左右子树中的每个值,则将递归调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,直到到达叶子为止。
要使递归调用遍历到叶子的最长路径,选择值,使 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 始终在左子结点上递归。当左子节点大于或等于右子节点时,它沿左分支。
例如,将根结点的值设为 0,将其他的所有结点的值设置为 1。这样, MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 将被调用 h h h 倍(其中 h h h 是堆高度,即从根到叶的最长路径中的边数),又因为每次调用为 Θ ( 1 ) \Theta(1) Θ(1),因此其运行时间为 Θ ( h ) \Theta(h) Θ(h),即 Θ ( lg ⁡ n ) \Theta(\lg n) Θ(lgn)
因为上面已经假设了一种情况,使得 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的运行时间为 Θ ( lg ⁡ n ) \Theta(\lg n) Θ(lgn),因此最坏的运行时间为 Ω ( lg ⁡ n ) \Omega(\lg n) Ω(lgn)


6.3 建堆

自底向上地使用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 将数组 A [ 1.. n ] A[1..n] A[1..n] 转换为一个最大堆。由练习 6.1-7 知,子数组 A [ ( ⌊ n / 2 ⌋ ) . . n ] A[(\lfloor n/2 \rfloor)..n] A[(n/2)..n] 是树的所有叶子,每个可看作是只含一个元素的堆。

BUILD-MAX-HEAP ( A ) \texttt{BUILD-MAX-HEAP}(A) BUILD-MAX-HEAP(A)
A . h e a p - s i z e = A . l e n g t h A.heap\text-size = A.length A.heap-size=A.length
for i = ⌊ A . l e n g t h / 2 ⌋ i = \lfloor A.length /2 \rfloor i=A.length/2 downto 1 1 1
MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY}(A, i) MAX-HEAPIFY(A,i)

可以使用循环不变式证明 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 的正确性。
循环不变式:在 2~3 行中 for 循环的每一次迭代开始时,结点 i + 1 , i + 2 , … , n i+1, i+2, \dots, n i+1,i+2,,n 都是一个最大堆的根。

分析 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 运行时间的紧确界:
性质1:有 n n n 个元素的堆的高度是 ⌊ lg ⁡ n ⌋ \lfloor \lg n \rfloor lgn,见练习6.1-2
性质2:在任意高度 h h h 上,最多有 ⌈ n / 2 h + 1 ⌉ \lceil n / 2^{h+1} \rceil n/2h+1 个结点,见练习6.3-3

在高度为 h h h 的结点上调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 需要时间 O ( h ) O(h) O(h) BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 的总代价为
∑ h = 0 ⌊ lg ⁡ n ⌋ ⌈ n 2 h + 1 ⌉ O ( h ) = O ( n ∑ h = 0 ⌊ lg ⁡ n ⌋ h 2 h ) \sum_{h=0}^{\lfloor \lg n \rfloor} \lceil \frac{n}{2^{h+1}} \rceil O(h) = O(n\sum_{h=0}^{\lfloor \lg n \rfloor} \frac{h}{2^{h}}) h=0lgn2h+1nO(h)=O(nh=0lgn2hh)

通过替换公式 (A.8) 中的 x = 1 / 2 x=1/2 x=1/2 计算后面的和式,得
∑ h = 0 ∞ h 2 h = 1 / 2 ( 1 − 1 / 2 ) 2 = 2 \sum_{h=0}^{\infty} \frac{h}{2^{h}} = \frac{1/2}{(1-1/2)^2} = 2 h=02hh=(11/2)21/2=2

所以, BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 的运行时间为
O ( n ∑ h = 0 ⌊ lg ⁡ n ⌋ h 2 h ) = O ( n ∑ h = 0 ∞ h 2 h ) = O ( n ) O(n\sum_{h=0}^{\lfloor \lg n \rfloor} \frac{h}{2^{h}}) = O(n\sum_{h=0}^{\infty} \frac{h}{2^{h}}) = O(n) O(nh=0lgn2hh)=O(nh=02hh)=O(n)

因此,可以在线性时间内,将一个无序数组建成一个最大堆。

练习

6.3-1 以图6.3 作为范例,作出 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 作用在数组 A = < 5 , 3 , 17 , 10 , 84 , 19 , 6 , 22 , 9 > A = < 5, 3, 17, 10, 84, 19, 6, 22, 9 > A=<5,3,17,10,84,19,6,22,9> 上的过程的图示。
解:
BuildMaxHeap

6.3-2 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 的第2行代码中,为什么循环下标 i i i 是从 ⌊ A . l e n g t h / 2 ⌋ \lfloor A.length /2 \rfloor A.length/2 降到 1 1 1,而不是从 1 1 1 增加到 ⌊ A . l e n g t h / 2 ⌋ \lfloor A.length /2 \rfloor A.length/2
解: 在调用函数 MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY}(A, i) MAX-HEAPIFY(A,i) 时,保证以结点 i i i 为根的子树是最大堆。

6.3-3 证明:在任一含有 n n n 个结点的堆中,最多有 ⌈ n / 2 h + 1 ⌉ \lceil n / 2^{h+1} \rceil n/2h+1 个高度为 h h h 的结点。
证: 由练习6.1-2 知,含有 n n n 个元素的堆的高度是 H = ⌊ lg ⁡ n ⌋ H = \lfloor \lg n \rfloor H=lgn
设高度为 h h h 的结点在第 x x x 层,则 x = H − h x = H - h x=Hh
x x x 层最多有 2 x − 1 2^{x-1} 2x1 个结点,
2 x − 1 = 2 H − h − 1 = 2 H / 2 h + 1 = 2 ⌊ lg ⁡ n ⌋ / 2 h + 1 ≤ 2 lg ⁡ n / 2 h + 1 = n / 2 h + 1 ≤ ⌈ n / 2 h + 1 ⌉ 2^{x-1} = 2^{H-h-1} = 2^H/2^{h+1} = 2^{\lfloor \lg n \rfloor}/2^{h+1} \le 2^{\lg n}/2^{h+1} = n / 2^{h+1} \le \lceil n / 2^{h+1} \rceil 2x1=2Hh1=2H/2h+1=2lgn/2h+12lgn/2h+1=n/2h+1n/2h+1


6.4 堆排序算法

HEAPSORT ( A ) \texttt{HEAPSORT}(A) HEAPSORT(A)
1 BUILD-MAX-HEAP ( A ) \texttt{BUILD-MAX-HEAP}(A) BUILD-MAX-HEAP(A)
2 for i = A . l e n g t h i = A.length i=A.length downto 2 2 2
3  exchange A [ 1 ] A[1] A[1] with A [ i ] A[i] A[i]
4   A . h e a p - s i z e = A . h e a p - s i z e − 1 A.heap\text-size = A.heap\text-size - 1 A.heap-size=A.heap-size1
5   MAX-HEAPIFY ( A , 1 ) \texttt{MAX-HEAPIFY} (A, 1) MAX-HEAPIFY(A,1)

HEAPSORT \texttt{HEAPSORT} HEAPSORT 过程运行时间为 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)。因为,调用 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 的时间为 O ( n ) O(n) O(n),调用 n − 1 n-1 n1 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,每次调用需要时间 O ( lg ⁡ n ) O(\lg n) O(lgn)

C++ 实现堆排序算法:

#include <iostream>
using namespace std;

int heap_size;

void MaxHeapify(vector<int>& A, int i) {
	int left = (i << 1) + 1;
	int right = left + 1;
	int largest = i;
	if (left < heap_size && A[left] > A[i]) {
		largest = left;
	}
	if (right < heap_size && A[right] > A[largest]) {
		largest = right;
	}
	if (largest != i) {
		swap(A[i], A[largest]);
		MaxHeapify(A, largest);
	}
}

// 迭代实现 MaxHeapify 
void MaxHeapify2(vector<int>& A, int i) {
	while (i < heap_size) {
		int left = (i << 1) + 1;
		int right = left + 1;
		int largest = i;
		if (left < heap_size && A[left] > A[i]) {
			largest = left;
		}
		if (right < heap_size && A[right] > A[largest]) {
			largest = right;
		}
		if (largest != i) {
			swap(A[i], A[largest]);
			i = largest;
		}
		else {
			break;
		}
	}
}

void BuildMaxHeap(vector<int>& A) {
	heap_size = A.size();
	for (int i = A.size() / 2; i >= 0; --i) {
		MaxHeapify(A, i);
	}
}

void Heapsort(vector<int>& A) {
	BuildMaxHeap(A);
	for (int i = A.size() - 1; i >= 1; --i) {
		swap(A[0], A[i]);
		heap_size = heap_size - 1;
		MaxHeapify(A, 0);
	}
}

int main() {
	vector<int> A{5, 13, 2, 25, 7, 17, 20, 8, 4};
	Heapsort(A);
	for (int i = 0; i < A.size(); ++i) {
		cout << A[i] << " ";
	}
	cout << endl;
}
练习

6.4-1 以图6.4 作为模型,作出 HEAPSORT \texttt{HEAPSORT} HEAPSORT 作用在数组 A = < 5 , 13 , 2 , 25 , 7 , 17 , 20 , 8 , 4 > A = < 5, 13, 2, 25, 7, 17, 20, 8, 4 > A=<5,13,2,25,7,17,20,8,4> 上操作的图示。
解:
Heapsort

6.4-2 如果使用下面的循环不变式,讨论 HEAPSORT \texttt{HEAPSORT} HEAPSORT 的正确性:
在 2~5 行中的 for 循环的每次迭代开始时,子数组 A [ 1.. i ] A[1..i] A[1..i] 是一个最大堆,包含 A [ 1.. n ] A[1..n] A[1..n] i i i 个最小元素,子数组 A [ i + 1.. n ] A[i+1 .. n] A[i+1..n] 包含 A [ 1.. n ] A[1..n] A[1..n] n − i n-i ni 个最大元素,且已排序。
解: 正确。
初始化: 在第一迭代前, i = n i = n i=n。因为已调用 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP,所以 A [ 1.. n ] A[1..n] A[1..n] 是一个最大堆,包含 n n n 个最小元素,子数组 A [ n + 1.. n ] A[n+1 .. n] A[n+1..n] 为空,包含 0 个已排序的最大元素。
保持: A [ 1 ] A[1] A[1] A [ 1.. i ] A[1..i] A[1..i] 中最大的元素,且 A [ 1.. i ] A[1..i] A[1..i] 中的元素都比 A [ i + 1.. n ] A[i+1..n] A[i+1..n] 中的元素小,所以交换 A [ 1 ] A[1] A[1] A [ i ] A[i] A[i] 的值,得 A [ i . . n ] A[i..n] A[i..n] 包含 n − i + 1 n-i+1 ni+1 个元素,且已排序。 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的调用保持了 A [ 1.. i − 1 ] A[1..i-1] A[1..i1] 的最大堆性质。在 for 循环中递减,为下一次迭代重新建立了循环不变式。
终止: 过程终止时, i = 1 i=1 i=1。根据循环不变式,知 A [ 1 ] A[1] A[1] A [ 1.. n ] A[1..n] A[1..n] 中最小的元素, A [ 2.. n ] A[2..n] A[2..n] 是已排序的 n − 1 n-1 n1 个元素,所以 A [ 1.. n ] A[1..n] A[1..n] 是已排序的 n n n 个元素。

6.4-3 数组 A A A 元素个数为 n n n,如果 A A A 已按升序排序,那么作用在 A A A 上的 HEAPSORT \texttt{HEAPSORT} HEAPSORT 的运行时间是多少?如果 A A A 降序排序呢?
解: 如果 A A A 是升序排序, HEAPSORT \texttt{HEAPSORT} HEAPSORT 过程运行时间为 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)。因为,建堆时每次调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 都要进行最大次数的交换,时间为 O ( n ) O(n) O(n);接着调用 n − 1 n-1 n1 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,每次调用需要时间 O ( lg ⁡ n ) O(\lg n) O(lgn)
如果 A A A 是降序排序, HEAPSORT \texttt{HEAPSORT} HEAPSORT 过程运行时间为 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)。因为,建堆时每次调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 只进行一次交换,共 ⌊ n / 2 ⌋ \lfloor n /2 \rfloor n/2 次,时间为 O ( n ) O(n) O(n);接着调用 n − 1 n-1 n1 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,每次调用需要时间 O ( lg ⁡ n ) O(\lg n) O(lgn)

6.4-4 证明: HEAPSORT \texttt{HEAPSORT} HEAPSORT 的最坏情况运行时间是 Ω ( n lg ⁡ n ) \Omega(n \lg n) Ω(nlgn)
证: 由练习6.2-6,知 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 的最坏情况的运行时间为 Ω ( lg ⁡ n ) \Omega(\lg n) Ω(lgn)
BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 调用 ⌊ n / 2 ⌋ \lfloor n /2 \rfloor n/2 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,所以,它运行时间为 Ω ( n lg ⁡ n ) \Omega(n \lg n) Ω(nlgn)
接着调用 n − 1 n-1 n1 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY,运行时间为 Ω ( n lg ⁡ n ) \Omega(n \lg n) Ω(nlgn)
所以, HEAPSORT \texttt{HEAPSORT} HEAPSORT 的最坏情况运行时间是 Ω ( n lg ⁡ n ) \Omega(n \lg n) Ω(nlgn)

6.4-5 证明:当所有元素都不同时, HEAPSORT \texttt{HEAPSORT} HEAPSORT 的最佳情况运行时间是 Ω ( n lg ⁡ n ) \Omega(n \lg n) Ω(nlgn)
证: 当所有元素都不同时,建队后,因为将 A [ 1 ] A[1] A[1] A [ i ] A[i] A[i] 进行了交换,所以每次调用 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY 需要时间为 Θ ( lg ⁡ i ) \Theta(\lg i) Θ(lgi),其中, i = n − 1 , n − 2 , . . . , 1 i = n-1,n-2,...,1 i=n1,n2,...,1
所以建队后的运行时间为 ∑ i = n − 1 1 Θ ( lg ⁡ i ) = Θ ( lg ⁡ ( ( n − 1 ) ! ) ) \sum_{i=n-1}^{1} \Theta(\lg i) = \Theta(\lg((n-1)!)) i=n11Θ(lgi)=Θ(lg((n1)!))
由斯特林公式
斯特林公式
可得出 n ! n! n! n n n^n nn 复杂度相同,所以建队后的运行时间为 Θ ( lg ⁡ ( n n ) ) = Θ ( n lg ⁡ n ) = Ω ( n lg ⁡ n ) \Theta(\lg(n^n)) = \Theta(n\lg n) = \Omega(n \lg n) Θ(lg(nn))=Θ(nlgn)=Ω(nlgn)


【算法导论】目录

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值