zoukankan      html  css  js  c++  java
  • 《算法导论》 第二章 练习题 Exercise

    2.2-1


    Θ(n^3)

    2.2-2


      

    for j = 1 to A.length - 1    
        min = j            
    //  The minimum value of sequence A [j..A.length-1] is added to the end of sequence A [1..j-1]
        i = j + 1           
        while i < A.length      
            if A[i] < A[min]           
            min = i
        if A[j] != A[min]
            swap A[j] and A[min]    

      

      该算法维持的循环不变式:

        I)初始化:循环第一次迭代前,j = 1,子序列 A[1..j-1] 为空。这个序列包含了 j-1 -1 +1 =0 个序列 A[j..A.length-1]中最小的元素。

        II)保持:该算法从序列 A[j..A.length] 中选取一个最小值 A[min] 加入到序列 A[1..j-1] 的末尾,所以在每一次 swap 操作后,子序列 A[1..j] 将包含 j 个元素,且序列 A 的第 j 小的值位于 A[1..j] 第 j 位。随后 j++,为下次迭代重新建立起了循环不变式。

        III)终止:循环结束时 j = A.length,此时在子序列 A[1..j-1] 中有 j-1 = A.length-1 个最小值且第 A.length-1 小的值位于第 A.length-1 位,而对于 A[1..A.length] 而言,剩下一个A[A.length],而这个第 A.length 小的值刚好位于第 A.length 位。故无需对第 A.length 位运行该算法,因为当循环结束时第 A.length 位自然就排序好了。

      因为选择排序是对 A[j..A.length] 进行选取最小值的操作,所以对于最好和最坏的情况,运行时间是一样的。

      由于增长量级最受 while i < A.length 的影响,而这一句的执行次数是  

      所以运行时间是 Θ(n^2)

        

    2.2-3


      平均情况的运行时间的求法:  ,其中 i 是问题规模为 n 的实例, P(i) 是实例 i 的概率,而T(i) 是实例 i 的运行时间。

      

      假设序列中的每一个元素都有固定 p 的概率是我们需要查找的元素v。一个序列有 k 个元素,若前 k-1 个元素都不是我们想要的元素,则第 k 个元素是我们想要的元素。这意味着当步数为 k 且它是我们想要的元素的概率是  

      但也有可能出现所有元素都不是我们想要的元素,它的概率是  

      通过将步数乘以它对应情况发生的概率,得到期望值关于步数的函数:

      最坏情况很明显是遍历序列,需要查找 A.length 步,运行时间是 Θ(A.length)

      接下来计算平均情况,我们先把上面的期望函数化简,化简过程利用了双求和、等差求和与等比求和:

      

      因为 > 0,所以 E(steps) <  ,又因为 A.Length ≥ 1,所以

      因此,对于一个给定上下界的序列,它平均情况的运行时间的增长量级与 p 和 A.length 有关,因为我们在一开始就设了 p 是固定值即 p = Θ(1),所以现在只考虑序列长度对运行时间的影响,有 T(n) = Θ(A.length)

      当然求平均时间还有另一种的办法,假设这个序列中一定存在你想要的元素,且每一个位置的元素是该元素的概率都是等可能性的,在这种情况下,最坏情况的查找次数与运行时间是不变的,而平均情况的查找次数是   步,运行时间即是 Θ(A.length)

    2.2-4


      

      算法刚开始时候先检测数据,如果数据满足特殊条件则直接输出相对应结果,比如一个排序算法,如果输入的数据是顺序的,那么不用再排序而是直接输出即可。

    2.3-1


      

    mergeSort (A, p , r)
        if p < r
            q = ⌊p+r/2//向下取整
            mergeSort (A, p, q )
            mergeSort (A, q+1, r)
            merge (A, p, q, r)

      归并排序遵循分治法,在每层递归都有三个步骤:

      I)分解:将数组A分解成2个子问题,每个问题的规模是原问题的一半,即 A1 = <3, 41, 52, 26>,A2 = <38, 58, 9, 49>

      II)解决: 使用归并排序对两个子问题进行递归地排序,当待排序的长度为1时,递归“回升”,不做任何操作,因为此时长度为1的序列都是有序的。

      III)合并:合并两个已经排好序的子序列。

      base case :p == r 时序列仅有一个元素,可看作已经是排序好的了,终止递归

      操作流程图如下:

      

    2.3-2


     

    merge (A, p, q, r)
        n1 = q - p + 1
        n2 = r - q
        let L[1..n1], 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+i]
        i = 1
        j = 1
        for k = p to r
            if i <= n1 and j <= n2
                if L[i] < R[j]
                    A[k] = L[i]
                    i = i + 1
                else
                    A[k] = R[j]
                    j = j + 1
            else if i > n1
                    A[k] = R[j]
                    j = j +1
            else if j > n2
                    A[k] = R[i]
                    i = i + 1

    2.3-3


      

      由题意,设 n = 2k 。当 k = 1 时,n = 2,T(2) = 2 lg2 = 2,T(n) = n lgn 成立。

      假设当 k 时成立,即 

      当 k+1 时, 有如下推导:

      

      所以对n = 2k,k是任意自然数,该递归式的解都是  T(n) = n lgn  

    2.3-4


      

      递归版本的插入排序如下:

    insertionSort (A, p, r)
        //base case
        if p == r
            return NIL
        //recursive case
        if p < r
            q = r - 1 
            insertionSort (A, p, q)
            
        //let A[1..i] be sorted
        key = A[r]
        i = q
        while i > 0 and A[i] > key
            A[i+1] = A[i]
            i = i - 1
        A[i+1] = key
        return A

      

      最坏运行情况是全部元素都是按逆序排序,需要移动 n-1 次,此时插入步骤所耗的时间将是 Ι(n-1) = Θ(n)

      

    2.3-5


    二分查找迭代版本  

    binarySearch(A, q, r, v)
        while q <= r
            mid = ⌊q+r⌋ / 2
            if A[mid] == v
                reutrn mid
            if A[mid] < v
                q = mid + 1
            else
                r = mid - 1
        return NIL

    二分查找递归版本

    binarySearch (A, p, r, v)
        if p > r
            return NIL
        mid = ⌊p + r⌋ / 2
      
       if A[mid] == v return mid

    //recursive case
    if A[mid] < v return binarySearch (A, mid+1, r, v) if A[mid] > v return binarySearch (A, q, mid-1, v)

      由于二分查找的递归树树高为 lgn,而每次查找的时间是 Θ(1),所以它的最坏情况运行时间的递归式是,时间总代价为 c lgn ∈ Θ(lgn) 

    2.3-6


      不行。虽然我们可以在插入算法的基础上(插入算法令 A[1..i-1] 保持有序)利用二分查找去优化查询关键字的效率,优化的算法如下

    biInsertionSort (A)
        for i = 2 to A.length
            key = A[i]
            low = 1
            high = i - 1
            // find a smaller value than A[i] of sorted sequence A[1..i-1]
            while (low <= high)
                mid = (low + high) / 2
                if A[mid] < key
                    low = mid + 1
                if A[mid] > key
                    high = mid - 1
            // move A[mid..i-1] backwards such that A[i] insert A[mid] 
            for j = mid to i-1
                A[j+1] = A[j]
            A[mid] = key

      但是可以发现,在查找的步骤后还需要将整个数组向后移动,而移动是线性的,每一轮都是 Θ(n),所以最坏情况的运行时间还是 Θ(n2)。

    2.3-7


        

    //先归并排序后二分查找
    TwoNumberSum (S, x)
        mergeSort(S, 1, n)
        for j = 1 to n
            tar = x - S[j]
            j2 = BinarySearch(S, tar)
            if j2 != NIL
            break
        if j2 == NIL
            return false
        else
            return true

      我采用的是先将S排序后,进行一轮循环查找 x-S[j],循环不变式的终止条件是找到 x-S[j] 的值。排序采用的是归并排序,最坏情况的时间代价为 Θ(n lgn),而查找采用的是二分查找,最坏情况的时间代价为 Θ(lgn),由于查找经历了 n 轮,所以查找的总代价 Θ(n lgn)。所以该算法最坏情况的时间代价为 Θ(n lgn)  

      

      还有一种办法,先归并排序,后利用两个指针分别指向头和尾,往中间扫描  

    TwoNumberSum (S, x)
        mergeSort (S, 1, n)
        i = 1
        j = n
        while i < j
            if A[i] + A[j] == x
                return true            
            if A[i] + A[j] < x
                i = i + 1
            if A[i] + A[j] > x
                j = j - 1
        return false

      可以看得出来,查找的时间仅需 Θ(n),所以时间总代价受排序时间代价的影响,为Θ(n lgn)

      扫描的原理很简单,先设 mi,j  : A[i] + A[j] < S,Mi,j : A[i] + A[j] > S 。由于序列已排序,则 mi,j ⇒∀k < j  都有 mi,k  成立,并且 Mi,j ⇒ ∀k > i 都有 Mk,j 成立

      

      其实这道题是LeetCode #1 原题,还可以利用哈希存储优化时间复杂度,相关的博客链接:http://www.cnblogs.com/Bw98blogs/p/8058931.html

    ————全心全意投入,拒绝画地为牢
  • 相关阅读:
    多线程中thread和runnable
    安装hive 个人遇到的问题小问题
    Linux 简单命令学习记录
    shell脚本简单学习教训经验
    @AutoWired使用
    <jsp:directive.page>标签
    Hibernate session.saveOrUpdate()方法
    无法连接远程mysql问题
    svn版本控制
    Hql中占位符(转)
  • 原文地址:https://www.cnblogs.com/Bw98blogs/p/8277212.html
Copyright © 2011-2022 走看看