2-1 在归并排序中对小数组采用插入排序
虽然归并排序的最坏情况运行时间为 θ ( n log 2 n ) \theta(n \log_2 n) θ(nlog2n),插入排序的最坏情况运行时间为 θ ( n 2 ) \theta(n^2) θ(n2),但是插入排序中的常数因子使得它在 n n n 较小时,运行得要更快一些。因此,在归并排序算法中,当子问题足够小时,采用插入排序来使递归的叶变粗是有意义的。考虑对归并排序做这样的修改,即采用插人排序策略,对 n / k n/k n/k 个长度为 k k k 的子列表进行排序,然后再用标准的合并机制将它们合并起来,此处 k k k 是一个待定的值。
-
证明: 插入排序最坏情况可以在 θ ( n k ) \theta(nk) θ(nk) 时间内排序每一个长度为 k k k 的 n / k n/k n/k 个子表。
解: 插入排序的最坏时间复杂度为 θ ( n 2 ) = a n 2 + b n + c \theta(n^2) = an^2 + bn +c θ(n2)=an2+bn+c,对于每一个长度为 k k k 的序列都有 θ ( k 2 ) = a k 2 + b k + c \theta(k^2) = ak^2 + bk +c θ(k2)=ak2+bk+c,那么 n / k n/k n/k个序列则为
n k θ ( k 2 ) = n k ( a k 2 + b k + c ) = a n k + b n + c n k = θ ( n k ) \begin{array}{ll} \dfrac{n}{k}\theta(k^2) &=& \dfrac{n}{k}(ak^2 + bk +c) \\ &=& ank + bn + \dfrac{cn}{k} \\ &=& \theta(nk) \end{array} knθ(k2)===kn(ak2+bk+c)ank+bn+kcnθ(nk) -
**证明:**这些子列表可以在 θ ( n log 2 ( n / k ) ) \theta(n \log_2 (n/k)) θ(nlog2(n/k)) 最坏情况时间内完成合并。
解: 标准的递归排序需要按其递归式分解,直到问题规模下降到1,每个子问题的代价为 c c c,有 n n n 个子问题,递归深度为 log 2 ( n / 1 ) \log_2(n/1) log2(n/1),总代价为: c n log 2 n + c n cn\log_2n+cn cnlog2n+cn。现在,插入-递归排序时,不需要将问题规模下降到1而是k,每个子问题的代价为 c k ck ck,有 n / k n/k n/k 个子问题,递归深度为 log 2 ( n / k ) \log_2(n/k) log2(n/k),那么总代价为 c k × n k × ( log 2 ( n k ) + 1 ) = c n log 2 ( n / k ) + c n ck\times\dfrac{n}{k}\times(\log_2(\dfrac{n}{k}) + 1)= cn\log_2(n/k) + cn ck×kn×(log2(kn)+1)=cnlog2(n/k)+cn。最坏时间为: θ ( n log 2 ( n / k ) ) \theta(n \log_2 (n/k)) θ(nlog2(n/k))。 -
假定修改后的算法的最坏情况运行时间为 θ ( n k + n log 2 ( n / k ) ) \theta(nk + n \log_2(n/k)) θ(nk+nlog2(n/k)),要使修改后的算法于标准的归并排序具有相同的运行时间,作为 n n n 的一个函数,借助 θ \theta θ 符号, k k k 的最大值是什么?
解:
KaTeX parse error: Expected & or \\ or \cr or \end at position 69: …ta(n\log_2n) \\\̲ ̲\theta(k + \log…
由 θ \theta θ 可得,当 k < log 2 n k < \log_2n k<log2n 时,插入-归并排序和标准的归并排序具有相同的运行时间。 k k k 的最大值为 log 2 n \log_2n log2n。 -
在实践中, k k k 的值应该如何选取?
解: 选择插入排序比归并排序快的最大数组长度作为 k k k值。
2-2 冒泡排序算法的正确性
冒泡排序(bubblesort)算法是一种流行但低效的排序算法,它重复地交换相邻的未按次序排序的元素。
- Bubble-Sort(A) 伪代码
for i =1 to A.length -1
for j = A.length downto i + 1
if A[j] < A[j-1]
exchange A[j] with A[j-1]
-
假设 A ′ A^\prime A′表示
Bubble-Sort(A)
的输出,为了证明bubble-Sort的正确,我们必须证明它将终止并且有:
A ′ [ 1 ] ≤ A ′ [ 2 ] ≤ A ′ [ 3 ] ≤ ⋯ ≤ A ′ [ n ] A^\prime[1] \le A^\prime[2] \le A^\prime[3] \le \cdots \le A^\prime[n] A′[1]≤A′[2]≤A′[3]≤⋯≤A′[n]
其中 n = A . l e n g t h n=A.length n=A.length。为了证明bubble-Sort确实完成了排序,我们还需要证明什么?下面两部分将证明上述不等式。
解: 证明BUBBLESORT的正确性,除了证明不等式(2.3),还需要证明 A ′ A\prime A′中所有元素都来自 A A A。 -
为第2-4行的
for
循环精确地说明一个循环不变式,并证明该循环不变式成立。
解:
内循环不变式: 在开始2-4行的for
循环时,子数组 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]中第 j j j个元素是最小的。同时元素 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]是原来在位置 j j j到 A . l e n g t h A.length A.length的元素。
初始化: 循环第一次迭代之前,有 j = A . l e n g t h j=A.length j=A.length,循环不变式成立,子数组 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]中只包含一个元素。必然是最小。
保持: 循环时,进入 A [ j ] A[j] A[j]和 A [ j − 1 ] A[j-1] A[j−1]的判断,使得循环结束时 A [ j − 1 ] ≤ A [ j ] A[j-1] \le A[j] A[j−1]≤A[j],那么 A [ j − 1 ] A[j-1] A[j−1]小于 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]中任意元素,j的值减少1为下次循环迭代维持了循环不变式。
结束: 终止时, j = i j=i j=i。也就是 A [ i . . A . l e n g t h ] A[i..A.length] A[i..A.length]是由原来的 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]的元素组成。且 A [ j ] A[j] A[j]为 A [ j . . A . l e n g t h ] A[j..A.length] A[j..A.length]中最小元素。 -
使用2步骤证明地循环不变式地终止条件,为第1-4行的
for
循环说明一个循环不变式,该不变式将使你能证明不等式。
解:
外循环不变式: 在开始1-4行的for
循环时,子数组 A [ 1 , i − 1 ] A[1, i-1] A[1,i−1]中已经按从小到大的顺序排序。且 A [ i , A . l e n g t h ] A[i, A.length] A[i,A.length]的元素都大于等于 A [ 1 , i − 1 ] A[1, i-1] A[1,i−1]。
初始化: 循环第一次迭代之前,有 i = 1 i=1 i=1,所以子数组 A [ 1.. i ] A[1..i] A[1..i]为空,满足循环不变式。
保持: A [ 1 , i − 1 ] A[1, i-1] A[1,i−1]已排序,进入内循环,根据内循环不变式子数组 A [ i , A . l e n g h t ] A[i,A.lenght] A[i,A.lenght]中的第i位是最小值,于是A[1,i]构成新的已排序序列,剩余 A [ i , A . l e n g t h ] A[i, A.length] A[i,A.length]满足大于等于 A [ 1 , i − 1 ] A[1, i-1] A[1,i−1],为下次循环迭代维持了循环不变式。
终止: 终止时 i = A . l e n g t h − 1 i = A.length-1 i=A.length−1。根据循环不变式,子数组 A [ 1 , i ] A[1, i] A[1,i]中已经从小到大排序,而由保持可得 A [ A . l e n g t h ] A[A.length] A[A.length]是比 A [ i ] A[i] A[i]大的数。所以序列A已排序。 -
冒泡排序的最坏运行时间是多少?与插入排序的运行时间相比,其性能如何?
**解: **
Bubble-Sort | 代价 | 次数 |
---|---|---|
for i = 1 to A.length -1 | c 1 c_1 c1 | n n n |
for j = A.length downto i + 1 | c 2 c_2 c2 | ∑ j = 2 n j \sum\limits^n_{j=2}j j=2∑nj |
if A[j] < A[j-1] | c 3 c_3 c3 | ∑ j = 2 n ( j − 1 ) \sum\limits^n_{j=2}(j-1) j=2∑n(j−1) |
exchange A[j] with A[j-1] | c 4 c_4 c4 | ∑ j = 2 n ( j − 1 ) \sum\limits^n_{j=2}(j-1) j=2∑n(j−1) |
KaTeX parse error: Expected & or \\ or \cr or \end at position 116: …n_{j=2}(j-1) \\\̲ ̲&=& \theta(n^2)…
最坏情况运行时间和插入排序一样,都是
θ
(
n
2
)
\theta(n^2)
θ(n2)。 但是插入排序最好情况运行时间为
θ
(
n
)
\theta(n)
θ(n),冒泡排序则为
θ
(
n
2
)
\theta(n^2)
θ(n2)。
2-3 霍纳(Horner) 规则的正确性
给定系数 a 0 , a 1 , ⋯   , a n a_0, a_1, \cdots, a_n a0,a1,⋯,an 和 x x x 的值,代码片段
y = 0
for i = n downto 0
y = ai + x * y
实现了用于求值多项式
P
(
x
)
=
∑
k
=
0
n
a
k
x
k
=
a
0
+
x
(
a
1
+
x
(
a
2
+
⋯
+
x
(
a
n
−
1
+
x
a
n
)
⋯
 
)
)
P(x) = \sum\limits^n_{k=0}a_kx^k = a_0 + x(a_1 + x(a_2 + \cdots + x(a_{n-1} + xa_n)\cdots))
P(x)=k=0∑nakxk=a0+x(a1+x(a2+⋯+x(an−1+xan)⋯))
的霍纳规则。
- 借助
θ
\theta
θ记号,实现霍纳规则的以上代码片段的运行时间是多少?
解: T ( n ) = θ ( n ) T(n) = \theta(n) T(n)=θ(n) - 编号伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每个项。该算法的运行时间是多少?与霍纳规则相比,其性能如何?
解:
y = 0
for i = 0 to n
m = 1
for j = 1 to i
m = m * x
y = y + ai * m
运行时间: T ( n ) = θ ( n 2 ) T(n) = \theta(n^2) T(n)=θ(n2),比霍纳规则慢
- 考虑以下循环不变式:
在第2-3行for
循环每次迭代的开始有
y = ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k y = \sum\limits^{n-(i+1)}_{k=0}a_{k+i+1}x^k y=k=0∑n−(i+1)ak+i+1xk
把没有项的和式解释为等于0。遵照本章中给出的循环不变式证明的结构,使用该循环不变式来证明终止时有 y = ∑ k = 0 n a k x k y=\sum\limits^n_{k=0}a_kx^k y=k=0∑nakxk。
解:
初始化: i = n i = n i=n, y = 0 y = 0 y=0。循环不变式成立
保持: 第 i i i次迭代:
y = a i + x ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k = a i x 0 + ∑ k = 0 n − i − 1 a k + i + 1 x k + 1 = a i x 0 + ∑ k = 1 n − i a k + i x k = ∑ k = 0 n − i a k + i x k \begin{array}{ll} y &=& a_i + x\sum\limits^{n-(i+1)}_{k=0}a_{k+i+1}x^k \\ &=& a_ix^0 + \sum\limits^{n-i-1}_{k=0}a_{k+i+1}x^{k+1} \\ &=& a_ix^0 + \sum\limits^{n-i}_{k=1}a_{k+i}x^k \\ &=& \sum\limits^{n-i}_{k=0}a_{k+i}x^k \end{array} y====ai+xk=0∑n−(i+1)ak+i+1xkaix0+k=0∑n−i−1ak+i+1xk+1aix0+k=1∑n−iak+ixkk=0∑n−iak+ixk
终止: i = − 1 i = -1 i=−1,
y = ∑ k = 0 n − i − 1 a k + i + 1 x k = ∑ k = 0 n a k x k y = \sum\limits^{n-i-1}_{k=0}a_{k+i+1}x^k = \sum\limits^n_{k=0}a_kx^k y=k=0∑n−i−1ak+i+1xk=k=0∑nakxk - 最后证明上面给出的代码片段将正确地求由系数
a
0
,
a
1
,
⋯
 
,
a
n
a_0, a_1, \cdots, a_n
a0,a1,⋯,an 刻画地多项式地值。
解: 显然由第3问可知,根据循环不变式,终止时 y y y值恰好为所求多项式。
2-4 逆序对
假设 A [ 1.. n ] A[1..n] A[1..n] 是一个有 n n n 个不同数地数组。若 i < j i < j i<j 且 A [ i ] > A [ j ] A[i] > A[j] A[i]>A[j],则对偶 ( i , j ) (i,j) (i,j)称为A地一个逆序对。
-
列出数组 2 , 3 , 8 , 6 , 1 {2, 3, 8, 6, 1} 2,3,8,6,1 的5个逆序对。
解: (8,6);(2,1);(3,1);(8,1);(6,1) -
由集合 1 , 2 , ⋯   , n {1, 2, \cdots, n} 1,2,⋯,n中的元素构成的什么数组具有最多的逆序对?它有多少逆序对?
解: 构成 n , n − 1 , ⋯   , 2 , 1 {n, n-1, \cdots, 2, 1} n,n−1,⋯,2,1,最多有 ∑ 1 n − 1 n = n ( n − 1 ) 2 \sum\limits^{n-1}_{1}n = \dfrac{n(n-1)}{2} 1∑n−1n=2n(n−1)个逆序对。 -
插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?
解: 逆序对的数量,影响插入排序while
循环内的执行次数。也就是有多少逆序对,就要执行多少次内循环。所以,含有逆序对的数组排序时间为 θ ( n + d ) \theta(n + d) θ(n+d),其中 d d d 为数组中含有逆序对的数量。 -
给出一个确定在 n n n 个元素的任何排序中逆序对数量的算法,最坏情况需要 θ ( n log 2 n ) \theta(n\log_2n) θ(nlog2n) 时间。
解: 逆序对算法
# -*- coding:utf-8 -*-
import sys, random
def merge(array, begin, mid, end):
inversion = 0
a = array[begin : mid]
b = array[mid : end]
aIndex = 0
bIndex = 0
for i in range(begin, end):
if aIndex == len(a):
array[i: end] = b[bIndex:]
break
if bIndex == len(b):
array[i: end] = a[aIndex:]
break
if a[aIndex] <= b[bIndex]:
array[i] = a[aIndex]
aIndex += 1
else:
inversion += (mid + bIndex - i)
array[i] = b[bIndex]
bIndex += 1
return inversion
def mergeSort(array, begin, end):
inversion = 0
if (end - begin) >= 2:
mid = (begin + end) // 2
inversion += mergeSort(array, begin, mid)
inversion += mergeSort(array, mid, end)
inversion += merge(array, begin, mid, end)
return inversion
if __name__ == "__main__":
# array = random.sample([x for x in range(100)], 20)
array = [3, 41, 52, 26, 38, 57, 9, 49]
print(array)
print("inversion: ",mergeSort(array, 0, len(array)))
思路: 如果不包含逆序对,那么a子数组中的元素应该都小于b子数组。当a[aIndex] > b[bIndex]
时,b[bIndex]
在A数组移动的距离就是逆序对的数目。