2.1 插入排序
2.1-1
|
|
|
|
|
|
2.1-2
按非升序排序(而不是非降序)的过程INSERTION-SORT:
INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
// Insert A[j] into the sorted sequence A[1..j - 1].
i = j - 1
while i > 0 and A[i] < key
A[i + 1] = A[i]
i = i - 1
A[i + 1] = key
2.1-3
线性查找的伪代码:
LINEAR-SEARCH(A, v)
for i = 1 to A.length
if A[i] == v
return i
return NIL
循环不变式:
在第2~4行的for循环的每次迭代开始时,子数组A[1..i - 1]中的元素都不等于v。
2.1-4
形式化描述:
输入: 代表n位二进制整数的两个n元数组A和B。
输出: (n+1)元数组C使得C代表的二进制整数等于A和B代表的两个二进制整数之和。
伪代码:
ADD-INTEGER(A, B)
key = 0
for i = 1 to n
C[i] = (A[i] + B[i] + key) mod 2
key = ⌊(A[i] + B[i] + key) / 2⌋
C[n + 1] = key
return C
2.2 分析算法
2.2-1
函数用记号表示为。
2.2-2
选择算法伪代码:
SELECT-SORT(A)
for i = 1 to A.length - 1
smallest = i
for j = i + 1 to A.length
if A[j] < A[smallest]
smallest = i
exchange A[i] with A[smallest]
循环不变式:
在第2~5行的for循环的每次迭代开始时,A[i - 1]是数组中第i - 1小的元素。
因为当循环结束时,数组的前n - 1个数依次是数组中前n - 1小的元素,则第n个数是最大的数。所以只需要对前n - 1个元素,而不是对所有n个元素运行。
选择排序的最好情况与最坏情况运行时间都是。
2.2-3
假定要查找的元素等可能地为数组中的任意元素,线性查找平均需要检查输入序列的一半元素。最坏情况需要检查输入序列的所有元素。
线性查找的平均情况运行时间为,最坏情况运行时间为。
证明:
设线性查找需要检查输入序列的元素数为x。
- 在平均情况下,x取1~n中任一个数的概率都为1/n,则。所以线性查找的平均情况运行时间为。
- 在最坏情况下,x一直取n,所以线性查找的最坏情况运行时间为。
2.2-4
修改算法,使它测试输入是否满足某些特殊情况,如果有的话,输出一个预先计算的答案。
2.3 设计算法
2.3.1 分治法
2.3.2 分析分治算法
2.3-1
归并排序在数组A=<3, 41, 52, 26, 38, 57, 9, 49>上的操作:
2.3-2
重写过程MERGE:
MERGE(A, p, q, r)
n = 1
Let L[1..q - p + 1] and R[1..r - q] be new arrays
for i = p to q
L[n] = A[i]
n = n + 1
n = 1
for j = q + 1 to r
R[n] = A[j]
n = n + 1
i = 1
j = 1
for k = p to r
if i ≤ q - p + 1 and (j > r - q or L[i] ≤ R[j])
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
2.3-3
证明:
- 当时,成立。
- 若时,成立。 当时,成立。
所以递归式的解是。
2.3-4
插入排序的递归版本的最坏情况运行时间递归式:。
2.3-5
迭代二分查找伪代码:
BINARY-SEARCH(A, v)
i = 1
j = A.length
while i ≤ j
mid = ⌊(i + j) / 2⌋
if v == A[mid]
return mid
else if v < A[mid]
j = mid - 1
else
i = mid + 1
return NIL
证明:
假设是规模为的一个问题的最坏情况运行时间,因为,则。所以二分查找的最坏情况运行时间为。
2.3-6
可以使用二分查找来把插入排序的最坏情况总运行时间改进到。
思考题
2-1
a.证明:
因为插入排序排序长度为的单个子表的最坏情况运行时间为,则排序每个长度为的个子表的最坏情况运行时间为。所以插入排序最坏情况可以在时间内排序每个长度为的个子表。
b.因为合并2个长度为的子表的最坏情况运行时间为,在递归树的每一层有个长度为的子表,合并一层的所有子表的最坏情况运行时间为,递归树总共有层,则合并所有层的所有子表的最坏情况运行时间为。
c.要使修改后的算法与标准的归并排序具有相同的运行时间,即,则。所以的最大值是。
d.在实践中,应该选择插入排序比归并排序快的值。
2-2
a.还需要证明可以构成原数组。
b.第2~4行的for循环的循环不变式:
在开始第2~4行for循环的每次迭代时,是子数组中最小的元素。
证明:
初始化:在循环的第一次迭代之前,有,子数组中只有一个元素,所以是子数组中最小的元素。
保持:首先假设,因为是子数组中最小的元素,所以在第4行将和交换之后,是子数组中最小的元素。反之,若,已是子数组中最小的元素,则不交换和。
终止:终止时,根据循环不变式,是子数组中最小的元素。
c. 第1~4行的for循环的循环不变式:
在开始第1~4行for循环的每次迭代时,子数组已按从小到大的顺序包含数组中的个最小元素。
证明:
初始化:在循环的第一次迭代之前, 有,子数组为空。这个空的子数组包含数组的个最小元素。
保持:因为子数组已按从小到大的顺序包含数组中的个最小元素,第2~4行的for循环终止时,根据(b)部分证明的循环不变式,是子数组中最小的元素,所以子数组已按从小到大的顺序包含数组中的个最小元素。
终止:终止时,根据循环不变式,子数组已按从小到大的顺序包含数组中的个最小元素,则是数组中的最大元素,所以数组按从小到大的顺序包含数组中的个元素。
d. 冒泡排序的最坏情况运行时间是,其性能与插入排序相当。
2-3
a.实现霍纳规则的以上代码片段的运行时间是。
b.朴素的多项式求值算法:
POLYNOMIAL-EVALUATION(a, x)
y = 0
for i = 0 to n
z = 1
for j = 1 to i
z = z * x
y = y + a[i] * z
该算法的运行时间是,其性能比霍纳规则差。
c.证明:
初始化:在循环的的第一次迭代之前,有,。
保持:因为,所以在第3行将赋值给后,。
终止:终止时,根据循环不变式,。
d.证明:
因为当算法终止时有,所以上面给出的代码片段将正确地求由系数刻画的多项式的值。
2-4
a.数组<2, 3, 8, 6, 1>的5个逆序对:(1, 5)、(2, 5)、(3, 4)、(3, 5)、(4, 5)。
b.由集合{1, 2, ..., n}中的元素构成的已反向排序的数组具有最多的逆序对,有逆序对。
c.设插入排序的最佳情况运行时间为,输入数组中逆序对的数量为,则插入排序的运行时间,其中为交换数组中两个元素需要的时间。
d.确定在个元素的任何排列中逆序对数量的算法,最坏情况需要时间:
REVERSED-ORDER(A, p, r)
if p < r
q = ⌊(p + r) / 2⌋
reverse = REVERSED-ORDER(A, p, q) + REVERSED-ORDER(A, q + 1, r) +
REVERSED(A, p, q, r)
else
reverse = 0
return reverse
REVERSED(A, p, q, r)
reverse = 0
n = 1
Let L[1..q - p + 1] and R[1..r - q] be new arrays
for i = p to q
L[n] = A[i]
n = n + 1
n = 1
for j = q + 1 to r
R[n] = A[j]
n = n + 1
i = 1
j = 1
for k = p to r
if i ≤ q - p + 1 and (j > r - q or L[i] ≤ R[j])
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
reverse = reverse + (q - p - i + 2)
return reverse