[算法导论][思考题]2

2-1

(在归并排序中队小数组采用插入排序) 虽然归并排序的最坏情况运行时间为 Θ ( n 2 ) \Theta(n^2) Θ(n2),但是插入排序中的常量因子可能使得它在 n n n较小时,在许多机器上实际运行得更快。因此,在归并排序中当子问题变得足够小时,采用插入排序使得递归的叶变粗是有意义的。考虑对归并排序的一种修改,其中使用插入排序来排序长度为 k k k n / k n/k n/k个子表,然后使用标准的合并机制来合并这些子表,这里 k k k是一个特定的值。

  1. 证明:插入排序最坏情况可以在 Θ ( n k ) \Theta(nk) Θ(nk)时间内排序每个长度为 k k k n / k n/k n/k个子表。
  2. 表明在最坏情况下如何在 Θ ( n l o g 2 ( n / k ) ) \Theta(nlog_2(n/k)) Θ(nlog2(n/k))时间内合并这些子表。
  3. 假定修改后的算法的最坏情况运行时间为 Θ ( n k + n l o g 2 ( n / k ) ) \Theta(nk+nlog_2(n/k)) Θ(nk+nlog2(n/k)),要使修改后的算法与标准的归并排序有相同的运行时间,作为 n n n的一个函数,借助 Θ \Theta Θ记号, k k k的最大值是多少?
  4. 在实践中,我们应该如何选择 k k k

答:

  1. 在最坏情况下,使用插入排序排序一个长度为 k k k的列表的运行时间是 Θ ( k 2 ) \Theta(k^2) Θ(k2)。因此,在最坏情况下,排序 n / k n/k n/k个子列表(每个子列表的长度为 k k k)的运行时间是 Θ ( k 2 ⋅ n / k ) = Θ ( n k ) \Theta(k^2 \cdot n/k)=\Theta(nk) Θ(k2n/k)=Θ(nk)
  2. 我们有 n / k n/k n/k个子列表每一个子列表的长度是 k k k。为了合并这 n / k n/k n/k个已排序的子列表成为一个长度为 n n n的单一已排序的列表,我们必须每次合并2个子列表。如果两两为一组的子列表合并完了,就继续再两两一组。这需要花费我们 l o g 2 ( n / k ) log_2(n/k) log2(n/k)步,并且每一步需要比较 n n n个元素。所以,在最坏情况下,合并这些子列表所花费的时间是 Θ ( n ⋅ l o g 2 ( n k ) ) \Theta(n \cdot log_2(\frac{n}{k})) Θ(nlog2(kn))
  3. Θ ( n k + n ⋅ l o g 2 ( n k ) ) = Θ ( n ⋅ l o g 2 n ) \Theta(nk+n\cdot log_2(\frac{n}{k})) = \Theta(n\cdot log_2n) Θ(nk+nlog2(kn))=Θ(nlog2n)时,修改后的算法具有普通合并排序的算法复杂度。假定 k = Θ ( l o g 2 n ) k = \Theta(log_2n) k=Θ(log2n)
    Θ ( n k + n ⋅ l o g 2 ( n k ) ) = Θ ( n k + n ⋅ l o g 2 n − n ⋅ l o g 2 k ) \Theta(nk + n\cdot log_2(\frac{n}{k})) = \Theta(nk + n\cdot log_2n - n\cdot log_2k) Θ(nk+nlog2(kn))=Θ(nk+nlog2nnlog2k)
    Θ ( n ⋅ l o g 2 n + n ⋅ l o g 2 n − n ⋅ l o g 2 ( l o g 2 n ) ) \Theta(n\cdot log_2n + n\cdot log_2n - n\cdot log_2(log_2n)) Θ(nlog2n+nlog2nnlog2(log2n))
    = Θ ( 2 n ⋅ l o g 2 n − n ⋅ l o g 2 ( l o g 2 n ) ) =\Theta(2n\cdot log_2n - n\cdot log_2(log_2n)) =Θ(2nlog2nnlog2(log2n))
    = Θ ( n ⋅ l o g 2 n ) =\Theta(n\cdot log_2n) =Θ(nlog2n)
  4. k k k选定为当插入排序比合并排序更快时,子列表的最大长度。

2-2

(冒泡排序的正确性)冒泡排序是一种流行但低效的排序算法,它的作用是反复交换相邻的未按次序排列的元素。

BUBBLESORT(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]
  1. 假设 A ′ A' A表示BUBBLESORT(A)的输出。为了证明BUBBLESORT正确,我们必须证明它将终止并且有:
    A ′ [ 1 ] ⩽ A ′ [ 2 ] ⩽ A ′ [ 3 ] ⩽ ⋯ ⩽ A ′ [ n ] A'[1]\leqslant A'[2]\leqslant A'[3]\leqslant \cdots\leqslant A'[n] A[1]A[2]A[3]A[n]
    其中 n = A . l e n g t h n = A.length n=A.length。为了证明BUBBLESORT确实完成了排序,我们还需要证明什么?
    下面两部分将证明不等式。
  2. 为第2~4行的for循环精确地说明一个循环不变式,并证明该循环不变式成立。你地证明应该使用本章给出地循环不变式证明地结构。
  3. 使用(2)部分证明的循环不变式的终止条件,为第1~4行的for循环说明一个循环不变式,该不变式将使你能证明不等式。你的证明应该使用本章给出的循环不变式证明的结构。
  4. 冒泡排序的最坏情况运行时间是多少?与插入排序的运行时间相比,其性能如何?

答:

  1. A ′ A' A是由 A A A中的元素组成,且是按顺序排列的。
  2. 证明过程需要完善
  3. 循环不变式为第1-4行的for循环在第 i i i次循环之前, A [ 1.. i − 1 ] A[1..i - 1] A[1..i1]已排好序,且 A [ 1 ] A[1] A[1]为数组 A A A最小的元素,数组 A [ 2 ] A[2] A[2]为次小的元素,等等。数组 A A A剩余的元素 A [ i . . A . l e n g t h ] A[i..A.length] A[i..A.length]即为未排序的元素。初始化:在第1次循环之前, i i i的值是1,数组 A [ 1.. i − 1 ] A[1..i-1] A[1..i1]为空,因为在初始化的时候所有元素均未排序,故循环不变式成立。保持:在第 i i i次循环开始之前,数组 A [ 1.. i − 1 ] A[1..i-1] A[1..i1]中的依次从小到大排列,且是数组 A A A中最小的 i − 1 i-1 i1个元素。在第 i i i次循环中,数组 A [ i + 1.. A . l e n g t h ] A[i + 1..A.length] A[i+1..A.length]中最小的元素会逐步冒泡“上升”到数组 A A A的第 i i i个位置。所以,当前循环结束后,第 i i i个位置的位置即为数组 A A A中“第 i i i小”的元素。因此,循环不变式保持成立。终止:循环终止条件为 i = = A . l e n g t h i == A.length i==A.length,此时 A [ 1.. i − 1 ] A[1..i-1] A[1..i1]中的元素即为数组 A A A中的所有元素除最大的元素之外的所有元素,且已按从小到大的顺序排列,因 A [ A . l e n g t h ] A[A.length] A[A.length]就是数组 A A A中的仅剩的元素,也是最大元素,所以数组 A A A中的所有元素均已被排序。
  4. 冒泡排序的结果是数组 A A A中的元素以从小到大的顺序排列。最坏情况是,原始情况下数组 A A A以从大到小的顺序排列。这个时候,对于第2行的for循环的每次迭代,第3行的的判断均成立,故第4行的交换均执行。所以,对于第1行的for循环的 i i i,第4行的执行次数为 A . l e n g t h − i A.length - i A.lengthi,假设交换数组元素所需的运行时间为c,则冒泡排序总共所需运行时间为 ∑ 1 n − 1 c ( n − i ) = c n ( n − 1 ) 2 \sum _{1}^{n-1}c(n - i) = \frac{cn(n-1)}{2} 1n1c(ni)=2cn(n1)。故,冒泡排序的最坏情况运行时间是 Θ ( n 2 ) \Theta(n^2) Θ(n2),与插入排序相比,所需的运行时间是一样的。

2-3

(霍纳(Horner)规则的正确性)给定系数 a 0 , a 1 , ⋯ , a n a_0,a_1,\cdots,a_n a0a1an 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_{k = 0}^{n}a_kx^k = a+0 + x(a_1 + x(a_2 + \cdots + x(a_{n-1} + xa_n)\cdots)) P(x)=k=0nakxk=a+0+x(a1+x(a2++x(an1+xan)))
的霍纳规则。

  1. 借助 Θ \Theta Θ记号,实现霍纳规则的以上代码片段的运行时间是多少?
  2. 编写伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每个项。该算法的运行时间是多少?与霍纳规则相比,其性能如何?
  3. 考虑以下循环不变式:
    在第2~3行for循环每次迭代的开始有
    y = ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k y = \sum_{k = 0}^{n-(i + 1)}a_{k + i + 1}x^k y=k=0n(i+1)ak+i+1xk
    把没有项的和式解释为等于0。准找本章中给出的循环不变式证明的结构,使用该循环不变式来证明终止时有 y = ∑ k = 0 n a k x k y = \sum_{k = 0}^{n}a_kx^k y=k=0nakxk
  4. 最后证明上面给出的代码片段将正确地求由系数 a 0 , a 1 , ⋯ , a n a_0,a_1,\cdots,a_n a0a1an刻画地多项式的值。

答:

  1. 实现霍纳规则的以上代码片段的运行时间是 Θ ( n ) \Theta(n) Θ(n)
  2. 朴素的多项式求值算法:
sum = 0
pow = 1
for 0 downto n
	sum += ai * pow
	pow *= x

该算法的运行时间是 Θ ( n ) \Theta(n) Θ(n)。该算法的性能霍纳规则是一样的。

  1. 第2~3行for循环不变式为每次迭代开始之前 y = ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k y = \sum_{k = 0}^{n - (i + 1)}a_{k + i + 1}x^k y=k=0n(i+1)ak+i+1xk。初始化:当第一次循环之前,和式中没有项,故y = 0。保持:在第 i i i次迭代之后,我们有
    y = a i + x ∑ k = 0 n − ( i + 1 ) x k y = a_i + x\sum_{k = 0}^{n - (i + 1)}x^k y=ai+xk=0n(i+1)xk
    = a i x 0 + x ∑ k = 0 n − i − 1 a k + i + 1 x k + 1 = a_ix^0 + x\sum_{k = 0}^{n - i - 1}a_{k + i + 1}x^{k + 1} =aix0+xk=0ni1ak+i+1xk+1
    = a i x 0 + ∑ k = 1 n − i a k + i x k = a_ix^0 + \sum_{k = 1}{n - i}a_{k + i}x^k =aix0+k=1niak+ixk
    = ∑ k = 0 n − i a k + i x k = \sum_{k = 0}^{n - i}a_{k + i}x^k =k=0niak+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_{k = 0}^{n - i - 1}a_{k + i + 1}x^k = \sum_{k = 0}^na_kx^k y=k=0ni1ak+i+1xk=k=0nakxk

  2. 循环的不变量是一个等于一个具有给定系数的多项式的和。

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 A A的一个逆序对(inversion)。

  1. 列出数组 < 2 , 3 , 8 , 6 , 1 > <2, 3, 8, 6, 1> <2,3,8,6,1>的5个逆序对。
  2. 有集合 1 , 2 , ⋯   , n {1, 2, \cdots, n} 1,2,,n中的元素构成的什么数组具有最多的逆序对?她有多少逆序对?
  3. 插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。
  4. 给出一个确定在 n n n个元素的任何排列中逆序对数量的算法,最坏情况需要 Θ ( n ⋅ l o g 2 n ) \Theta(n\cdot log_2n) Θ(nlog2n)时间。(提示:修改该归并算法。)

答:

  1. < 1 , 5 > 、 < 2 , 5 > 、 < 3 , 4 > 、 < 3 , 5 > 、 < 4 , 5 > <1, 5>、<2, 5>、<3, 4>、<3, 5>、<4, 5> <1,5><2,5><3,4><3,5><4,5>
  2. 如果集合 1 , 2 , ⋯   , n {1, 2, \cdots, n} 1,2,,n中的元素是以降序排列的,那么组成的逆序对最多。它有 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1)个逆序对。
  3. 如果输入数组中的逆序对数量越多,则插入排序的运行时间就越大。对于数组中的的逆序对 < i , j > <i, j> <i,j>,在应用插入排序的时候,为了保证数组按正序排列,必定会交换逆序对 < i , j > <i, j> <i,j>。每一个逆序对会花费时间 t t t,那么在排序数组的时候,总共花费的时间就是逆序对的数目 m ⋅ t m\cdot t mt。所以,插入排序的运行时间与输入中逆序对的数量之间呈正比关系。
INVERSION-MERGE(A, p, q, r)
	result = 0
	for m = p to to q
		for n = q + 1 to r
			if A[m] < A[n]
				break
			result++
	
	n1 = q - p + 1
	n2 = r - q
	let L[1 .. n1] and R[1 .. n2] be new arrays
	for i = 1 to n1
		L[i] = A[p + i - 1]
	for j = 1 to n2
		R[j] = A[q + j]
	i = 1
	j = 1
	for k = p to r
		if L[i] <= R[j]
			A[k] = L[i]
			i = i + 1
		else
			A[k] = R[j]
			j = j + 1
	
	return result

INVERSION-SORT(A, p, r)
	result = 0
	if p < r
		q = (p + r) / 2
		result += INVERSION-SORT(A, p, q)
		result += INVERSION-SOFT(A, q + 1, r)
		result += INVERSION - MERGE(A, p, q, r)
	return result
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值