2-1 在归并排序中对小数组采用插入排序
【描述】虽然归并排序最坏情况运行时间为 ,而插入排序的最坏运行时间是 ,但插入排序的常量因子可能使它在较小规模的 时,在许多机器上执行得更快。因此在归并排序中当子问题足够小时,使用插入排序来使递归的叶变粗是有意义的。考虑对归并排序进行一种修改,其中使用插入排序来排序长度为 的 个子表,然后使用标准的合并机制来合并这些子表,这里 是一个特定的值。
(1)证明:插入排序最坏情况下可以在 时间内排序每个长度为 的 个子表。
(2)证明:在最坏情况下合并这些子表所需要的运行时间为 。
(3)假定修改后的算法的最坏情况运行时间为 ,要使修改后的算法与标准的归并排序具有相同的运行时间,作为 的一个函数,借助 记号, 的最大值是什么?
(4)在实践中,我们应该如何选择 ?
【2-1 解答】
(1)证明:
已知插入排序在规模为 时,最坏情况运行时间为 ,因此每个长度为 的子表最坏情况运行时间为 。那么, 个子表最坏情况运行时间为 。
(2)证明:
归并排序会构造一个二叉树,自底向上进行归并,每一层两两合并左右节点有序子表至根节点,无论左右每一对有序子表长度如何,一层的所有子表总长度总是为 ,因此。每一层的比较次数总是为 ,每一层的比较耗时为 。
由于归并排序拆分出了 个子表,因此其构造的二叉树的层数为 。
结合上述结论即可证明,整棵二叉树全部运行耗时为 。
(3)求解:
令 ,则有 。
当 时,等式成立。
当 时, 中后一项增长级数不如前一项,所以 可以忽略不计,因此等式成立。
故,。
(4)求解:
所选取的 应该小于 。
2-2 冒泡排序的正确性
【描述】冒泡排序是一种流行但低效的排序算法,它的作用是反复交换相邻的未按次序排列的元素。伪代码如下:
BUBBLE-SORT(A)
1 for i = 1 to A.length - 1
2 for j = A.length downto i + 1
3 if A[j] < A[j-1]
4 exchange A[j] with A[j-1]
(1)假设 表示 BUBBLE-SORT(A) 的输出。为了证明 BUBBLE-SORT 正确,我们必须证明它将终止,并且有:
其中,。为了证明 BUBBLE-SORT 确实完成了排序,我们还需要证明什么?
(2)为第 2~4 行的 for 循环精确说明一个循环不变式,并证明该循环不变式成立。
(3)使用(2)证明的循环不变式终止条件,为 1~4 行的 for 循环说明一个循环不变式,该不变式能使你证明不等式(2.2)。试着证明。
(4)冒泡排序最坏运行时间是多少?与插入排序相比性能如何?
【2-2 解答】
(1)解答:
还需要证明 中元素均来自于 。
(2)证明:
- 循环不变式:子数组 中的元素仍然在 中,且 。
- 初始化:第一次迭代之前,,子数组 中只有一个 ,显然,成立。
- 保持:迭代一个给定值的 。首先假设此次迭代前循环不变式成立,那么根据循环不变式, 是 中最小的元素。第 3 ~ 4 行代码表示如果 就交换 和 ,显然这使得 成为 中最小的元素。由于唯一改变子数组 的操作仅仅是那次可能发生的交换操作,且在迭代开始时, 中的元素最初都是在 中,所以在迭代开始时 中的元素最初都是在 中。然后 自减,准备开始进入下一轮迭代。
- 终止:循环终止时 。根据循环不变式,,并且 中的元素最初都在 中。 所以在 2 ~ 4 行的 for 循环执行结束过后, 将是 中最小的元素。
证毕。
(3)证明:
- 循环不变式:子数组 包含了 中前 小的所有元素,并且它们是已排好序的。 中包含了 中余暇的元素。
- 初始化:第一次迭代之前 。子数组 为空,循环不变式显然成立。
- 保持:迭代一个给定值的 。首先假设此次迭代前循环不变式成立,那么根据循环不变式, 包含了 中前 小的所有元素,并且它们是已排好序的。第一部分已经证明:在执行 2 ~ 4 行的 for 循环后 是 中最小的元素。所以在执行了 2 ~ 4 行的 for 循环后 中就包含了 中前 i 小的所有元素,并且它们已经排好序。子数组 就包含了 个 中余下的元素。
- 终止:循环终止时 。所以根据循环不变式, 中包含了 中前 小的元素(即 的全部元素),并且它们是排好序的。
证毕。
(4)解答:
冒泡排序的最坏运行时间是 ;
最坏情况下,冒泡排序与插入排序耗时几乎差不多,但是最好情况下,冒泡排序比插入排序性能差。
2-3 霍纳(horner)规则的正确性
【描述】给定系数 和 的值,代码片段:
1 y = 0
2 for i = n downto 0
3 y = a[i] + x * y
实现了用于求值多项式
的霍纳规则。
(1)说明上述代码片段的运行时间。
(2)编写伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每一个项。并说明它的运行时间是多少?与霍纳规则相比,性能如何?
(3)考虑以下循环不等式:在第 2~3 行 for 循环每次迭代的开始有
把没有项的和式解释为等于 0。遵循本章中给出的循环不变式证明的结构,使用该循环不变式来证明终止时有 。
(4)最后证明上面给出的代码片段将正确地求由系数 描述的多项式的值。
【2-3 解答】
(1)解答:
运行时间为 。
(2)解答:
朴素的多项式求值算法伪代码:
NAIVE-HORNER(a, x)
y = 0
for k = 0 to a.length
temp = 1
for i = 1 to k
temp = temp * x
y = y + a[k] * temp
return y
上述朴素多项式求值算法的运行时间为 ,比霍纳规则慢了一个数量级。
(3)证明:
- 初始化:第一次迭代前,,成立
- 保持: 在第 次循环的结尾,有:
- 终止:循环终止时,,有:
证毕。
(4)显然,循环的不变量是与给定系数的多项式相等的和。
2-4 逆序对
【描述】假设 是一个有 个不同数的数组。若 且 ,则对偶 称为 A 的一个逆序对。
(1)列出数组 {2,3,8,6,1} 的 5 个逆序对。
(2)由集合 {1, 2, ..., n} 中元素构成的什么数组具有最多的逆序对?它有多少个逆序对?
(3)插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。
(4)给出一个确定在 个元素的任何排列中逆序对数量的算法,最坏情况需要 时间。(提示:修改归并排序)
【2-4 解答】
(1)解答:
(2)解答:
全递减序列 具有最多的逆序对。
逆序对个数:
(3)解答:
插入排序运行时间与输入数组中逆序对数量呈正相关。
因为逆序对正巧一一对应着插入排序过程中元素之间的比较操作。
(4)解答:
MERGE-INVERSIONS(A, p, q, r)
m = q - p + 1
n = r - q
let L[1...m+1] and R[1...n+1] be new arrays
for i = 1 to m
L[i] = A[p + i - 1]
for j = i to n
R[j] = A[q + j]
L[m+1] = INF
R[n+1] = INF
i = 1
j = 1
inversions = 0
for k = p to r
if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
inversions = inversions + m - i + 1
A[k] = R[j]
j = j + 1
return inversions
COUNT-INVERSIONS(A, p, r)
if p < r
q = floor((p + r) / 2)
left = COUNT-INVERSIONS(A, p, q)
right = COUNT-INVERSIONS(A, q+1, r)
inversions = MERGE-INVERSIONS(A, p, q, r) + left + right
return inversions