6.1 堆
6.1-1
在高度为h的堆中,元素个数最多是,最少是
。
6.1-2
证明:。
6.1-3
证明:在最大堆中,某个结点的值至多与其父结点一样大。经过递推易得,结点的值小于等于它的任一祖先。所以,在最大堆的任一子树中,该子树所包含的最大元素在该子树的根结点上。
6.1-4
假设一个最大堆的所有元素都不相同,那么该堆的最小元素应该位于它的叶结点。
6.1-5
一个已排好序的数组是一个最小堆。
6.1-6
值为<23,17,14,6,13,10,1,5,7,12>的数组不是一个最大堆,因为第4个元素小于第9个元素。
6.1-7
证明:考虑下标为的结点的左孩子的下标:
,所以下标为
的结点是叶结点,则下标大于等于
的结点都是叶结点。考虑下标为
的结点的左孩子的下标:
,所以下标为
的结点不是叶结点,则下标小于等于
的结点都不是叶结点。综上:当用数组表示存储n个元素的堆时,叶结点下标分别是
,
,...,n。
6.2 维护堆的性质
6.2-1
6.2-2
MIN-HEAPIFY(A, i)
l = LEFT(i)
r = RIGHT(i)
if l ≤ A.heap-size and A[l] < A[i]
smallest = l
else smallest = i
if r ≤ A.heap-size and A[r] < A[smallest]
smallest = r
if smallest ≠ i
exchange A[i] with A[smallest]
MIN-HEAPIFY(A, smallest)
MIN-HEAPIFY与MAX-HEAPIFY的运行时间相等。
6.2-3
当元素A[i]比其孩子的值都大时,调用MAX-HEAPIFY(A,i)会在比较完元素A[i]和其孩子的大小之后结束,不会进行第二次递归调用。
6.2-4
因为,所以
,调用MAX-HEAPIFY(A,i)会在计算完LEFT(i),RIGHT(i)之后结束,不会进行第二次递归调用。
6.2-5
用循环控制结构取代递归,重写MAX-HEAPIFY代码。
MAX-HEAPIFY(A, i)
while i ≤ A.heap-size/2
l = LEFT(i)
r = RIGHT(i)
if A[l] > A[i]
largest = l
else largest = i
if A[r] > A[largest]
largest = r
if largest ≠ i
exchange A[i] with A[largest]
i = largest
else break
6.2-6
证明:对于n个结点的堆,可以通过对每个结点设定恰当的值,使得从根结点到叶结点路径上的每个结点都会递归调用MAX-HEAPIFY。因此,MAX-HEAPIFY的最坏情况运行时间等于堆的高度,即为。
6.3 建堆
6.3-1
6.3-2
因为每次调用MAX-HEAPIFY(A,i)后,可能会导致,从而不符合最大堆性质,因此要求BUILD-MAX-HEAP中第2行的循环控制变量i是从
到1递减。
6.3-3
证明:
当h=0,即叶结点的个数为。
假设对于h=i-1,结论成立。即对于任一包含n个元素的堆中,至多有个高度为i-1的结点。当h=i时,对于任一包含n个元素的堆,先将其叶结点全部剪去,得到的堆有
个结点。旧堆中高度为i的结点在新堆高度为i-1,根据假设,新堆中至多有
个高度为i-1的结点,即旧堆中至多有
个高度为i的结点。
综上,对于任一包含n个元素的堆中,至多有个高度为h的结点。
6.4 堆排序算法
6.4-1
6.4-2
初始化:在第一次循环迭代之前,,子数组A[1..n]是一个包含了数组A[1..n]中第n小元素的最大堆,而子数组A[n+1..n]包含了数组A[1..n]中已排序的0个最大元素。
保持:因为每次循环都把根结点从子数组A[1..i]中取出加入到子数组A[i+1..n]中,而且MAX-HEAPIFY维护了子数组A[1..i]是一个最大堆的性质,所以子数组A[1..i]是一个包含了数组A[1..n]中第i小元素的最大堆,而子数组A[i+1..n]包含了数组A[1..n]中已排序的n-i个最大元素。
终止:过程终止时,i=1,子数组A[1]包含了数组A[1..n]中的最小元素,而子数组A[2..n]包含了数组A[1..n]中已排序的n-1个最大元素,所以数组A[1..n]已经全部有序。
6.4-3
对于一个按升序或降序排列的包含n个元素的有序数组A来说,HEAPSORT的时间复杂度都是。
6.4-4
证明:在最坏情况下,即对一个按升序排列的包含n个元素的有序数组A进行HEAPSORT,首先调用BUILD-MAX-HEAP的时间复杂度是,而n-1次调用MAX-HEAPIFY,每次的时间为
,所以HEAPSORT的时间复杂度是
。
6.5 优先队列
6.5-1
HEAP-EXTRACT-MAX先将堆A的大小和1比较,因堆A的大小大于等于1,故不触发错误。在将A[1]即堆中的最大值的值保存在变量max中后,将A[1]替换为A[A.heap-size]即堆中的最小值。再将堆A的大小减1,然后调用MAX-HEAPIFY维护最大堆的性质,最后返回max的值。
6.5-2
MAX-HEAP-INSERT(A,10)先将堆A的大小加1,然后将A[A.heap-size]即新增的元素设为-∞,最后调用HEAP-INCREASE-KEY(A,A.heap-size,10)将元素10交换至正确的位置。
6.5-3
用最小堆实现最小优先队列。
HEAP-MINIMUM(A)
return A[1]
HEAP-EXTRACT-MIN(A)
if A.heap-size < 1
error "heap underflow"
min = A[1]
A[1] = A[A.heap-size]
A.heap-size = A.heap-size - 1
MIN-HEAPIFY(A, 1)
return min
HEAP-DECREASE-KEY(A, i, key)
if key > A[i]
error "new key is larger 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)
MIN-HEAP-INSERT(A, key)
A.heap-size = A.heap-size + 1
A[A.heap-size] = +∞
HEAP-DECREASE-KEY(A, A.heap-size, key)
6.5-4
因为任何实数值都比-∞大,把关键字设为-∞后,调用HEAP-INCREASE-KEY时就不会触发错误。
6.5-5
初始化:在第一次循环迭代开始前,将A[i]的值设为key,因为调用HEAP-INCREASE-KEY时,A[1..A.heap-size]是满足最大堆性质的,所以如果子数组A[1..A.heap-size]违背最大堆的性质,只有一个可能:A[i]大于A[PARENT(i)]。
保持:因为在每次循环迭代开始前,都先将A[i]和A[PARENT(i)]比较大小,如果A[PARENT(i)]<A[i],则交换A[i]和A[PARENT(i)]的值,然后将i的值设为PARENT(i),此时以结点i为根结点的子树满足最大堆性质。所以如果子数组A[1..A.heap-size]违背最大堆的性质,只有一个可能:A[i]大于A[PARENT(i)]。
终止:过程终止时,i≤1或者A[PARENT(i)]≥A[i]。如果i≤1,此时结点i已经没有父结点,所以子数组A[1..A.heap-size]满足最大堆的性质;如果A[PARENT(i)]≥A[i],此时以结点PARENT(i)为根结点的子树满足最大堆性质,因为结点PARENT(i)及其父结点本就满足最大堆性质,所以子数组A[1..A.heap-size]满足最大堆的性质。
6.5-6
在HEAP-INCREASE-KEY的第5行的交换操作中,利用INSERTION-SORT内循环部分的思想,只用一次赋值就完成这一交换操作。
HEAP-INCREASE-KEY(A, i, key)
if key < A[i]
error "new key is smaller than current key"
while i > 1 and A[PARENT(i)] < key
A[i] = A[PARENT(i)]
i = PARENT(i)
A[i] = key
6.5-7
先按照加入的先后顺序给每个结点赋予一个优先级,给先加入的结点赋予低的优先级,给后加入的结点赋予高的优先级。
- 使用优先队列来实现队列:使用最小优先队列组织所有结点,每次有新的结点加入时,调用MIN-HEAP-INSERT;需要弹出结点时,调用HEAP-EXTRACT-MIN。
- 使用优先队列来实现栈:使用最大优先队列组织所有结点,每次有新的结点加入时,调用MAX-HEAP-INSERT;需要弹出结点时,调用HEAP-EXTRACT-MAX。
6.5-8
HEAP-DELETE(A,i)操作能够将结点i从堆A中删除。对于一个包含n个元素的堆,设计能够在时间内完成的HEAP-DELETE操作。
HEAP-DELETE(A, i)
A[i] = A[A.heap-size]
A.heap-size -= 1
MAX-HEAPIFY(A, i)
6.5-9
一个时间复杂度为的算法,它能够将k个有序链表合并为一个有序链表,这里n是所有输入链表包含的总的元素个数。
MERGE-ORDER-LIST(A, n, k)
let R be a new min-heap
i = 0
for j = 1 to n
i += 1
i %= k
if A[i].length ≥ 1
MIN-HEAP-INSERT(R, A[i][1])
A[i].REMOVE-HEAD()
思考题
6-1
a.当输入数据相同的时候,BUILD-MAX-HEAP和BUILD-MAX-HEAP'生成的堆不是总是一样。例如:当输入数据为<4,1,3,2,16,9,10,14,8,7>时,BUILD-MAX-HEAP和BUILD-MAX-HEAP'生成的堆分别如下左右图所示。
b.证明:在最坏情况下,即在按倒序排列的数组上调用BUILD-MAX-HEAP'时,每次调用MAX-HEAP-INSERT向堆中插入元素时,都需将其与其父结点交换直至根结点,在数组中第i个元素执行此操作的时间复杂度为。所以,在最坏情况下,调用BUILD-MAX-HEAP'建立一个包含n个元素的堆的时间复杂度是
。
6-2
a.在一个数组中表示一个d叉堆。
PARENT(i)
return ⌊i/d⌋
CHILD(i, k)
return di + k - 1
b.包含n个元素的d叉堆的高度是。
c.EXTRACT-MAX在d叉最大堆上的一个有效实现,它的时间复杂度为。因为除了时间复杂度为
的MAX-HEAPIFY以外,它的其他操作都是常数阶的。
MAX-HEAPIFY(A, i)
largest = i
for j = 1 to d
c = CHILD(i, j)
if c ≤ A.heap-size and A[c] > A[largest]
largest = c
if largest ≠ i
exchange A[i] with A[largest]
MAX-HEAPIFY(A, largest)
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-HEAPIFY(A, 1)
return max
d.INSERT在d叉最大堆上的一个有效实现,它的时间复杂度为。
INSERT(A, key)
A.heap-size = A.heap-size + 1
A[A.heap-size] = -∞
INCERASE-KEY(A, A.heap-size, key)
e.INCREASE-KEY(A,i,k)在d叉最大堆上的一个有效实现,它的时间复杂度为。
INCREASE-KEY(A, i, k)
if k < A[i]
error "new key is smaller than current key"
A[i] = k
while i > 1 and A[PARENT(i)] < A[i]
exchange A[i] with A[PARENT(i)]
i = PARENT(i)
6-3
a.包含元素为{9,16,3,2,4,8,5,14,12}的Young氏矩阵。
b.证明: 对于一个的Young氏矩阵Y来说,
。如果Y[1,1]=∞,则Y中元素都是∞即Y为空。
。如果Y[m,n]<∞,则Y中所有元素都小于∞即Y为满。
c.在 Young氏矩阵上时间复杂度为
的EXTRACT-MIN的算法实现。
MAX-HEAPIFY(Y, i, j)
if j + 1 ≤ Y.column and Y[i, j + 1] < Y[i, j]
smallest_x = i
smallest_y = j + 1
else smallest_x = i
smallest_y = j
if i + 1 ≤ Y.row and Y[i + 1, j] < Y[smallest_x, smallest_y]
smallest_x = i + 1
smallest_y = j
if smallest_x ≠ i or smallest_y ≠ j
exchange Y[i, j] with Y[smallest_x, smallest_y]
MAX-HEAPIFY(Y, smallest_x, smallest_y)
EXTRACT-MIN(Y)
if Y.row < 1 or Y.column < 1
error "Young matrix underflow"
min = Y[1, 1]
Y[1, 1] = Y[Y.row, Y.column]
Y[Y.row, Y.column] = ∞
MAX-HEAPIFY(Y, 1, 1)
return min
d.在时间内,将一个新元素插入到一个未满的
的Young 氏矩阵中。
INSERT(Y, key)
i = Y.row
j = Y.column
Y[i, j] = key
while i > 1 and j > 1
if Y[i, j - 1] < Y[i, j]
smallest_x = i
smallest_y = j - 1
else smallest_x = i
smallest_y = j
if Y[i - 1, j] < Y[smallest_x, smallest_y]
smallest_x = i - 1
smallest_y = j
if smallest_x ≠ i or smallest_y ≠ j
exchange Y[i, j] with Y[smallest_x, smallest_y]
i = smallest_x
j = smallest_y
e.先创建一个的Young 氏矩阵,将其所有元素初始化为∞。然后对
个数逐个调用INSERT插入到矩阵中即可。
f.一个时间复杂度为的算法,用来判断一个给定的数是否存储在
的Young 氏矩阵中。
IS-IN-MATRIX(Y, key)
i = 1
j = 1
while i ≤ A.row and Y[i, j] ≤ key
if Y[i, j] == key
return true
i = i + 1
i = i - 1
j = j + 1
while j ≤ A.column and Y[i, j] ≤ key
if Y[i, j] == key
return true
j = j + 1
return false