《算法导论》第四章 练习题 Exercise

4.1-1


 

  通过分析知由于 FIND-MAX-CROSSING-SUBARRAY 返回的是数组A最中间的两个负数组成的数组,则经过分治算法 FIND-MAXIMUM-SUBARRAY 处理后,返回的是一个长度为1的只含最大负数的数组。

 

4.1-2


   

  伪代码如下:

//Brute force to find max subArray
findMaxSubArray (A, low, high)
    max = -INF 
    for i = low to high
        sum = 0
        for j = i+1 to high
            sum = sum + A[j]
            if sum > max
                max = sum
                maxRight = i
                maxLeft = j

    return (max, maxRight, maxLeft) //return max subArray

 

 

4.1-3


   

  性能交叉点要根据具体而定,大概在长度为 20 的数组时候可以发现此后递归算法的时间要明显低于暴力算法。

  修改基本情况——当规模小于n0时采用暴力算法可以优化递归算法的运行时间,性能交叉点不会发生变化。

 

4.1-4


 

  允许结果返回空子数组的原因可能有二:1、该时期股票价格不变,无需买股票 2.该时期股票持续下跌,不买股票。

  在 FIND-MAXIMUM-SUBARRAY 7~11行的语句中,增加判断:如果三个 sum 值中最大值的是 0 或者负数 ,那么返回空数组,即代表该时期股票价格保持不变甚至持续下跌,不买股票。更巧妙的方法是在执行该算法时先进行一次线性扫描,如果没有一个正数存在,说明股票没有增长,那么就返回空数组,代表该时期不买股票。

 

4.1-5


 

  感觉这个算法是基于DP的思想,当前状态是由前面某一个阶段的状态转移决定的,子问题并不互相独立,也就是题目中说的:A[1..n+1] 的最大子数组要么是 A[1..n] 的最大子数组,要么是某一子数组 A[i..n+1] ( 1 <= i <= n+1) 。

  其实最大子数组问题等价于”最大连续和“ 问题,只不过还要把数组的右左索引记录下来,才可以得到一个数组。所以我们现在先把目标转到如何用DP求出最大连续和。

  如何求出 A[1..i] 的最大连续和?分析知,A[1..i] 的最大连续和要么是 A[1..i-1] 的最大连续和加上 A[i],要么就是 A[i]  。那么,设 b[i] 为最后一个元素是 A[i] 的数组的最大连续和,有 b[i] = max(b[i-1] + A[i], A[i]) ,看得出来,如果想要满足 b[i-1] + A[i] > A[i] ,那么就要满足 b[i-1] > 0 。

  可以看得出来,这里影响下一步决策的因素就是 b[i] ,我们把它叫做状态。

  举个例子,现有一数组 A = <-23, 18, 20, -7, 12, -5, -22> ,设最大连续和问题为状态,即 dp[i] 表示最后一个元素是A[i] 的数组的最大连续和,则有如下推导:

    • dp[1] = A[1] = -23 (边界条件,只有一个元素的数组的连续最大和就等于该元素)
    • dp[2] = max( dp[1] + A[2], A[2] ) = 18 (递归定义最优解 dp 的值)
    • dp[3] = max( dp[2] + A[3], A[3] ) =  38
    • dp[4] = max( dp[3] + A[4], A[4] ) = 31
    • dp[5] = max( dp[4] + A[5], A[5] ) = 43
    • dp[6] = max( dp[5] + A[6], A[6] ) = 38
    • dp[7] = max( dp[6] + A[7], A[7] ) = 16
    • ...
    • dp[i] = max( dp[i-1] + A[i], A[i] ) (状态转移方程)

  通过状态转移方程,我们可以得到数组 A[1..i] 的所有子数组的最大连续和。但是实际的编程中我们只需要存储所有最大连续和中的最大值,就不用数组存储了,用变量就足够了。

  求最大连续和算法的伪代码如下:

maxSeqSum (A)
    dp = A[1]        //存储最大连续和,A[1] 本身就是 i =1 时的最大连续和
    res = A[1]        //存储所有子数组最大连续和的最大值
    for i = 2 to A.length           
        if dp > 0        //对应 " dp[i-1] + A[i] > A[i] ” 的情况
            dp += A[i] 
        else              //对应 “ dp[i] + A[i] <= A[i] ”的情况
            dp = A[i]
        if dp > res
             res = dp
    return res

 

  这个时候,只要再计算索引,就可以解出 A 的最大子数组。计算索引的方法就很简单了,设 start、end 分别是最大子数组的左边界索引、右边界索引,每次需要缩短最大连续和数组为 A[i] 时就更新左边界为 i ,每次将A[i]添加到最大子数组的时候就更新右边界。需要注意的是求最大连续和的时候,扩充的元素不一定会添加到最大子数组里。

  求最大子数组算法的伪代码如下:

findMaxSubarray (A)
    dp = A[1]
    res = A[1]
    start = end = 1
    for i = 2 to A.length
        if dp > 0
            dp += A[i]
        else
            dp = A[i]
            start = i
        if dp > res
            res = dp
            end = i
    return (start, end, res)

 

 

  感觉 DP 确实很强,就这道题而言它比分治策略利用到更多的旧信息。  

 

4.2-1


 

  A11 = [1] ,A12 = [3] ,

  A21 = [7] ,A22 = [5] 。

  B11 = [6] ,B12 = [8],

  B21 = [4] ,B22 = [2]。

  S1 = B12 - B22 = [6],

  S2 = A11 + A12 = [4],

  S3 = A21 + A22 = [12],

  S4 = B21 - B11 = [-2],

  S5 = A11 + A22 = [6],

  S6 = B11 + B22 = [8],

  S7 = A12 - A22 = [-2],

  S8 = B21 + B22 = [6],

  S9 = A11 - A21 = [-6],

  S10 = B11 + B12 = [14] 。  

  P1 = A11 • S1 = [6],

  P2 = S2 • B22 = [8],

  P3 = S3 • B11 = [72],

  P4 = A22 •  S4 = [-10],

  P5 = S5 • S6 = [48],

  P6 = S7 • S8 = [-12],

  P7 = S9 • S10 = [-84]。 

  C11 = P5 + P4 - P2 + P6 = [18],

  C12 = P1 + P2 = [14],

  C21 = P3 + P4 = [62],

  C22 = P5 + P1 - P3 - P7 = [66],

  所以, 

 

4.2-2


  

  Strassen 算法的伪代码如下:

square-Matrix-Strassen (A, B)
    n = A.row     
    let C be a new n*n matrix
    if n == 1 //base case
        C11 = A11 • B11
    else //recursive case
        partition A, B, and C as in equations 4.9
        let S1, S2, S3, S4, S5, S6, S7, S8, S9, S10 be new n/2 * n/2 matrixs
        S1 = B12 - B22
        S2 =  A11 + A12
        S3 = A21 + A22
        S4 = B21 - B11
        S5 = A11 + A22
        S6 = B11 + B22
        S7 = A12 - A22
        S8 = B21 + B22
        S9 = A11 - A21
        S10 = B11 + B12
        P1 = square-Matrix-Strassen (A11, S1)
        P2 =  square-Matrix-Strassen (S2, B22)
        P3 =  square-Matrix-Strassen (S3, B11)
        P4 =  square-Matrix-Strassen (A22, S4)
        P5 =  square-Matrix-Strassen (S5, S6)
        P6 =  square-Matrix-Strassen (S7, S8)
        P7 =  square-Matrix-Strassen (S9, S10)
    C11 = P5 + P4 - P2 + P6
    C12 = P1 + P2
    C21 = P3 + P4
    C22 = P5 + P1 - P3 - P7
    return C

 

4.2-3


   

  如果矩阵规模 n 不是 2的幂的情况,那就填充 0 使之成为方阵。填充的时间主要花费在复制上,最多 O(n2) ,所以算法的运行时间还是 Θ(nlg7)    

 

4.2-4


 

  ..

 

4.2-5


  

  ..

 

4.2-6


 

   

  ..

 

4.2-7


  

  ..

 

4.3-1


 

  先吐槽一下,T(n) = T(n-1) + n,这不是插入排序(递归)的执行时间么,又在这见到了。

  猜测:T(n) <= c · n2

  证明:T(n) = T(n-1) + n <= c · (n-1)2 + n = c · n2 + (1-c) · n + (1-n)  <= c · n2 ,其中只要 c >= 1 、n >= 1 最后一步推导就成立。

  所以 T(n) = T(n-1) + n = O(n2)

 

4.3-2


 

  T(n) = T(⌈n/2⌉) + 1 ,这应该是二分查找(递归)的执行时间。

  猜测:T(n) <=  c · lgn

  证明:n = 1 为递归式的基本情况,T(1) = 1。n >= 2 时, T(n) = T(⌈n/2⌉) + 1 <= c · lg(n/2)  + 1 = c · lgn - c + 1 <= c · lgn ,其中只要 c >= 1 ,最后一步推导就会成立。  

  所以 T(n) = T(⌈n/2⌉) + 1 = O( lgn ) 

 

4.3-3


 

  猜测:T(n) >= c · n · lgn

  证明:n = 1 为递归式基本情况,T(1) = 2T(⌊n/2⌋) + n = 1 。当 n >= 2 时,有 T(n) = 2T(⌊n/2⌋) + n >= 2 · c · ( ⌊n/2⌋ · lg⌊n/2⌋ ) + n >= ?,我的思路到这就卡着了,因为向下取整的符号会让左边 <= 右边,让接下去的推导无法成立,。如果这里是向上取整的符号的话,就可以推出 2 · c · ( ⌈n/2⌉ · lg⌈n/2⌉ ) + n >= c · n · (lgn - 1)+ n =  c · n · lgn 。

  看来是要换猜想了。加上些常数如何?

  参考了别人的解法,新猜测:

  证明:还是一样,n = 1 为递归式的基本情况,T(1) = 2T(⌊n/2⌋) + n = 1 。当 n >= 2 时,T(n) = 2T(⌊n/2⌋) + n >= 2c · ((⌊n/2⌋ + 2) · (lg⌊n/2⌋ + 2) + n >= 2c · ((n/2 - 1 + 2) · (lg(n/2)  - 1 + 2) + n = 2c · ((n/2 + 1) · lgn) + n = c · (n + 2) · lgn + n >= c · n · lgn ,其中存在 c > 0,使得最后一步推导成立

  所以 T(n) = Ω( n·lgn )

 

4.3-4


 

  我前面的两道题都不对 n=1 进行归纳证明,而是把它们视为递归式中的基本情况而不是归纳证明的基本情况,即把 n=1 视为 ”边界条件“。然而可以通过做出不同的归纳假设,可以对边界条件进行归纳。下面是对 T(n) = 2T(⌊n/2⌋) + n 重新做出归纳,把边界条件 n = 1 包含到归纳证明里。

  猜测:T(n) <= c · n·lgn + n 。n 是一个低阶项,不影响渐进解。

  证明:T(n) = 2T(⌊n/2⌋) + n <= 2 · (c ·⌊n/2⌋ · lg⌊n/2⌋ + ⌊n/2⌋) + n <= 2 · (c · n/2 · lg(n/2) + n/2) + n = c · n · lgn  + (2-c) · n <= c · n · lgn + n ,当 c >= 1 推导成立。

  由 T(1) = 1 = c · 1 · lg1 + 1 = 1,可知边界条件 n =1 时归纳证明也成立。 

  

4.3-5


 

  ”严格递归式“的意思是要对归并排序的奇偶输入进行区分,并非都为 T(n) = 2T(⌊n/2⌋) + n ,而是 T(n) = T(⌊n/2⌋) + T(⌈n/2⌉) + n 。

  证明方法和 4.3-3 很类似,这就直接贴图了:

  

 

 

4.3-6


 

  猜想:T(n) <= c · (n - a) · lg(n-a)

  证明:T(n) =  2T(⌊n/2⌋ + 17) + n <=  2c · (⌊n/2⌋ + 17 - a) · lg(⌊n/2⌋ + 17 - a) + n <= 2c · (n/2 + 17 - a) · lg(n/2 + 17 - a) + n  = c · (n + 34 - 2a) · lg((n + 34 - 2a)/2) + n = c · (n + 34 - 2a) · lg((n + 34 - 2a)  - c · (n + 34 - 2a) + n <= c · (n + 34 - 2a) · lg((n + 34 - 2a) <= c · (n - a) · lg(n-a) 

  当 c > 1 、a >= 34 、n > n0 = f(a) 时,才有后三步的推导。

  所以 T(n) =  2T(⌊n/2⌋ + 17) + n = O(n · lgn)

  也就是说,在递归式 T 的参数中增减一个常数,对递归式的解影响不大。

 

4.3-7


  

  猜想: 

  证明:

    

  之后就无法缩放成猜想的形式了,gg。

  

  再次猜想,这次减去一个低阶项:

  证明:

    

  再缩放一步就是我们想要的形式:

  

4.3-8


 

  猜想:T(n) <= cn2

  证明:T(n) = 4T(n/2) + n <= 4c · (n/2)2 + n = c·n2 + n

  凉了,存在多余的 n 且没法通过缩放而删除。

 

  减去一个低阶项,再猜想T(n) <= cn2 - n

  证明:T(n) = 4T(n/2) + n <= 4 · (c(n/2)2 - n/2) + n = cn2 - n <= cn2

  得到 T(n) = 4T(n/2) + n 的解为 Θ(n^2)  

 

4.3-9


 

  偷了点懒,过程借用书上改变变量的例题与主定理。

  设 m = lgn ,由例题可知,T(2m) = 3T(2m/2) + m

  记 S(m) = T(2m) ,得 S(m) = 3S(m/2) + m

  通过主定理,该情况对应 case 1 ,有 S(m) = Θ(mlg3)

  从 S(m) 转换成 T(n) ,得 T(n) = T(2m) = S(m) = Θ(mlg3) = Θ(lglg3n)

 

4.4-1


 

  画出递归树,如下图:

   

  树的深度 = lgn ,每层有 3i 个结点,i = lgn 。最底层有 3lgn 个结点,其代价为 nlg3 · T(1) = Θ(nlg3) 。

  接下来就是计算每层代价之和,这步比较简单,唯一的坑点就是得到的数列的指数是大于 1 的,不能用无穷等比级数公式对其进行简化得到上界,推导的时候可能稍微慢了一些。

  该递归树得到渐进上界的推导过程:

    

  代入法还是比较坑,我先拿了 T(n) <= nlg3 作为猜想去证明,但是多了一个 n ,gg。那么就换猜想,猜想是:(n) <= nlg3 - n 。接下来就小菜一碟了,很简单的证明过程,不贴了。

 

4.4-2


  

  本题的递归树如下:

    

  树的深度 lgn ,叶子结点有 1 个, 叶子结点总代价为 (1/4)lgn·c·n,递归树总代价很容易求,这里直接贴图了:

    

  接下来用代入法验证,猜想为 T(n) <= cn,一发入魂。证明很简单,如下:

    

 

 

4.4-3


 

  这题可以猜的出来 T(n) = O(n^2) ,因为之前证明过递归式 T(n) = 2T(n/2) + n 中 T 的参数增减一个常量,它对执行时间没有显著影响。然后就可以当作普通的 T(n) = 4T(n/2) + n 去解,为 O(n2) 。

  如果想更严谨一点,考虑常量的话,那么先画出递归树,注意递归式 T(n/2 + 2) 的意思是每次分解成规模为 n/2 + 2 的子问题。画出递归树如下:

  

  

  把 T(n) 看成两个数列求和,前一个数列 n, 2n, 4n ...很好解决,但是后一个数列 8, 32 .... 的关系还不会很明显,最好再画出第四层并计算这一层的时间代价。第四层是 128,为了计算方便,让深度 0, 1, 2, 3 ... lgn - 1, lgn ,对应 0, 8, 32, 128 ... 22lgn-1, 22lgn+1 ,和式为   (由于最后一层叶子结点用于判断上界,这里先不计叶子结点那一层),求和要用到等比数列求和,高中的公式不能忘。

  计算叶子结点那一层的代价:

  

  所以总代价如下,很好求和的,这里我懒就不细写了:

  

   用代入法验证:

    Guess:

      T(n) <= cn^2 - an , a > 0

    Prove:

      T(n) = T(n/2 + 2) + n

          <=  c(n/2 + 2)2 - (a/2 - 1)n - 2a = c/4 · n2 - (a/2 - 1 - 2c)n + 4c - 2a        

        <= c/4 · n2   (a > 4c + 1, c> 0)

        = Θ(n2)

  

4.4-4


   

  这道题对中英文版会有差别,我书上的递归式是 T(n) = T(n-1) + 1 (中文版),而英文版的是T(n) = 2T(n-1) + 1。

  递归树的形式和 4.4-2 一样,是一条线。每一层的代价都是 c ,共递归 n-1 次,所以很容易得到 T(n) = O(n)

  代入法:

    Guess: T(n) <= cn

    prove: T(n) = T(n-1) + 1 <= cn -c + 1 <= n ,当 c >= 1 时最后一步推导成立。

 

4.4-5


 

  种缺失结点的递归树只能得到一个不精确的上界。

  把递归树的图画出来,取两种极端情况,一种是树高为 lgn ,此时上界应该是 O(n^2) ;一种树高是 n-1 ,此时上界应该是 O(2n) 。取更大的上界当作是本题的上界,并用代入法证明:

     Gress: T(n) <= c·2n - n 

     Prove: T(n) = T(n-1) + T(n/2) + n <= c · 2n-1 - c·  (n+1) - c · n/2 + c · 2n/2 + n <= c2n - c·n/2 <= c · 2n ,当 c>=1 且 n 足够大时最后两步推导成立

 

4.4-6


   

  递归树的图就是书上的图 P4-6。

  如果想得到下界,那么得找到最短路径,然后剪去最短路径终点那一层下方所有的枝,此时通过第一层的根结点到叶子结点之间的时间的总代价就是下界。

  最短路径也很好找到,就是最左边不断地把 n 化为 n/3 的那一条路径。此时,树的深度为 log3n ,叶子结点的数量是 2log3n ,每个叶子结点花费的时间代价是常数,且此时的树是完全二叉树,所以每一层时间代价都是 cn ,则有 T(n) = Ω(log3n · n) ,由于在这里 log3n 严格大于 log2n,所以又有 T(n) = Ω(n·lgn)

 

4.4-7


   

  ..

 

4.4-8


 

  ..

 

4.4-9


 

  ..

 

4.5-1


 

  a. T(n) = Θ(nlog42) = Θ(n^(1/2))

  b. T= Θ(lgn · nlog42) = Θ(lgn · n^(1/2))

  c. T(n) = Θ(n)

  d. T(n) = Θ(n^2)

 

4.5-2


  

  log4a < log27 ,意味着 a < 49 ( log2 相当于开根号,解 log4 相当于开两次根号),所以 a 的最大整数是 48

 

4.5-3


 

  nlogba = 1 ,f(n) = Θ(1) ,有 T(n) = Θ(nlogb· lgn) = Θ(lgn)

 

4.5-4


   

  不可以,不是多项式大于。

  画出递归树,根结点的时间代价为 n2 · lgn ,其叶子结点的时间代价为 Θ(n^2) 。发现得到的和式很不好化简,但是很容易 Guess: T(n) = O(n2 · lgn) ,那么就用代入法咯。

  之后就是证明的过程了:

    

 

 

4.5-5


 

  挺多的,但是我自己脑洞不够大,没想出来具体的。

 

4.6-1


 

  ..

 

4.6-2


  

  ..

 

4.6-3


 

  ... 

 

posted @ 2018-01-20 12:58  bw98  阅读(1402)  评论(0编辑  收藏  举报