算法导论 第二章 算法基础(4)思考题

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 是一个待定的值。

  1. 证明: 插入排序最坏情况可以在 θ ( 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)

  2. **证明:**这些子列表可以在 θ ( 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))

  3. 假定修改后的算法的最坏情况运行时间为 θ ( 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 &lt; log ⁡ 2 n k &lt; \log_2n k<log2n 时,插入-归并排序和标准的归并排序具有相同的运行时间。 k k k 的最大值为 log ⁡ 2 n \log_2n log2n

  4. 在实践中, k k k 的值应该如何选取?
    解: 选择插入排序比归并排序快的最大数组长度作为 k k k值。

2-2 冒泡排序算法的正确性

冒泡排序(bubblesort)算法是一种流行但低效的排序算法,它重复地交换相邻的未按次序排序的元素。

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^\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. 为第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[j1]的判断,使得循环结束时 A [ j − 1 ] ≤ A [ j ] A[j-1] \le A[j] A[j1]A[j],那么 A [ j − 1 ] A[j-1] A[j1]小于 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]中最小元素。

  3. 使用2步骤证明地循环不变式地终止条件,为第1-4行的for循环说明一个循环不变式,该不变式将使你能证明不等式。
    解:
    外循环不变式: 在开始1-4行的for循环时,子数组 A [ 1 , i − 1 ] A[1, i-1] A[1,i1]中已经按从小到大的顺序排序。且 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,i1]
    初始化: 循环第一次迭代之前,有 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,i1]已排序,进入内循环,根据内循环不变式子数组 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,i1],为下次循环迭代维持了循环不变式。
    终止: 终止时 i = A . l e n g t h − 1 i = A.length-1 i=A.length1。根据循环不变式,子数组 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已排序。

  4. 冒泡排序的最坏运行时间是多少?与插入排序的运行时间相比,其性能如何?
    **解: **

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=2nj
if A[j] < A[j-1] c 3 c_3 c3 ∑ j = 2 n ( j − 1 ) \sum\limits^n_{j=2}(j-1) j=2n(j1)
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=2n(j1)

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 , ⋯ &ThinSpace; , 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 ) ⋯ &ThinSpace; ) ) 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=0nakxk=a0+x(a1+x(a2++x(an1+xan)))
的霍纳规则。

  1. 借助 θ \theta θ记号,实现霍纳规则的以上代码片段的运行时间是多少?
    解: T ( n ) = θ ( n ) T(n) = \theta(n) T(n)=θ(n)
  2. 编号伪代码来实现朴素的多项式求值算法,该算法从头开始计算多项式的每个项。该算法的运行时间是多少?与霍纳规则相比,其性能如何?
    解:
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),比霍纳规则慢

  1. 考虑以下循环不变式:
    在第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=0n(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=0nakxk
    解:
    初始化: 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 &amp;=&amp; a_i + x\sum\limits^{n-(i+1)}_{k=0}a_{k+i+1}x^k \\ &amp;=&amp; a_ix^0 + \sum\limits^{n-i-1}_{k=0}a_{k+i+1}x^{k+1} \\ &amp;=&amp; a_ix^0 + \sum\limits^{n-i}_{k=1}a_{k+i}x^k \\ &amp;=&amp; \sum\limits^{n-i}_{k=0}a_{k+i}x^k \end{array} y====ai+xk=0n(i+1)ak+i+1xkaix0+k=0ni1ak+i+1xk+1aix0+k=1niak+ixkk=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\limits^{n-i-1}_{k=0}a_{k+i+1}x^k = \sum\limits^n_{k=0}a_kx^k y=k=0ni1ak+i+1xk=k=0nakxk
  2. 最后证明上面给出的代码片段将正确地求由系数 a 0 , a 1 , ⋯ &ThinSpace; , 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 &lt; j i &lt; j i<j A [ i ] &gt; A [ j ] A[i] &gt; A[j] A[i]>A[j],则对偶 ( i , j ) (i,j) (i,j)称为A地一个逆序对。

  1. 列出数组 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)

  2. 由集合 1 , 2 , ⋯ &ThinSpace; , n {1, 2, \cdots, n} 1,2,,n中的元素构成的什么数组具有最多的逆序对?它有多少逆序对?
    解: 构成 n , n − 1 , ⋯ &ThinSpace; , 2 , 1 {n, n-1, \cdots, 2, 1} n,n1,,2,1,最多有 ∑ 1 n − 1 n = n ( n − 1 ) 2 \sum\limits^{n-1}_{1}n = \dfrac{n(n-1)}{2} 1n1n=2n(n1)个逆序对。

  3. 插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?
    解: 逆序对的数量,影响插入排序while循环内的执行次数。也就是有多少逆序对,就要执行多少次内循环。所以,含有逆序对的数组排序时间为 θ ( n + d ) \theta(n + d) θ(n+d),其中 d d d 为数组中含有逆序对的数量。

  4. 给出一个确定在 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数组移动的距离就是逆序对的数目。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值