算法导论 第二章 算法基础

2.1 插入排序

问题:输入:n个数的一个序列;输出:输入序列的一个排序,满足非降序条件

插入排序算法:类似于扑克牌的排序。开始时左手为空且桌子上的牌面向下,每次从桌子上拿走一张牌,从右向左逐一比较再插入到合适的位置,左手上的牌总是原先桌子上排好序的牌。

def INSERTION_SORT(A): 
    for j in range(1, len(A)):
        key = A[j]
        i = j - 1
        while i >= 0 and A[i] > key: 
            A[i + 1] = A[i] 
            i = i - 1 
        A[i + 1] = key

循环不变式:类似数学归纳法,用于证明算法的正确性。包含三个部分:

1.初始化:循环的第一次迭代之前,它为真

2.保持:如果循环的末次迭代之前它为真,那么下一次迭代之前它仍然为真

3.终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的

用循环不变式验证插入排序的正确性(非正式的描述性验证):

1.初始化:当j=1时,数组A只包含一个数据它是排好序的,且该数据是原来数组中的第一个数据,循环不变式为真

2.保持:当j>1时,循环不变式若为真,将A[j]与其之前的数据比较,结果若为小则将之前的数据后移一位,直到找到合适的位置并插入A[j]的值,在开始j+1的插入之前循环不变式为真。

3.终止:当j = len(A)+1时,之前A中的所有数据都已经排好序


2.2 分析算法

RAM(radom access machine):对算法运行的机器的抽象,执行一条指令的时间都抽象为常数时间。在算法分析中忽略不同机器的差异,在RAM计算模型中去分析。

输入规模:输入的项数,依赖于研究的问题。

运行时间:RAM模型下一个算法对输入的执行的基本操作数,一个基本操作称为步。假设每一步的执行时间是常量时间。故对于算法而言其执行时间为某一行代码的执行时间和其执行次数的积,并对所有的行的执行时间求和。则得到一个执行时间T以输入规模n为自变量的函数(每步的执行时间为常量作为自变量的系数)。插入排序算法对于一个已经排好序的输入其执行时间为输入规模n的线性函数。对一个逆序的输入其执行时间为输入规模n的二次函数。

最好情况:存在一个输入使得算法能到达的最少的执行时间。

最坏情况:存在一个输入使得算法需要花费的最多时间。

平均情况:即输入的分布概率,得出的加权平均时间。

我们基于如下理由只考虑最坏的情况:1.一个算法的最坏情况运行时间是在仸何输入下运行时间的一个上界,即可以据此做出承诺。2.对于某些算法,最坏情况出现的是相当频繁的。3.大致上来看,“平均情况“通常不最坏情况一样差。如果一个算法的最坏情况运行时间要比另一个算法的低,我们常常就认为它的效率更高。

输入规模较小的情况下,算法的执行效率并不需要特别关注,当输入规模n增大到一定程度时,效率不同的算法之间执行时间表现出明显的差异。故在输入规模增大时,导致算法运行时间增加较多的,称为增长率较大。在对输入规模为n的算法运行时间的函数中对增长率影响的分析,对增长率贡献最大的为高阶的n项,为简化分析通常我们只关心其高阶项,使用theta函数来表示运行时间函数的最重要的因子。


2.3 设计算法

分治法:将问题分解为较小规模的但类似于原问题的子问题,递归的求解这些子问题,然后再合并这些子问题的解来建立原问题的解。分治法在每层递归时的步骤都有三个:

1.分解:将原问题分解为较小规模的实例

2.解决:递归求解各个子问题,子问题规模足够小时,直接求解

3.合并:用子问题的解构建为原问题的解

使用分治法的归并排序算法:

1.分解:将n个元素的序列排序分解为n/2个元素的两个子序列的排序问题

2.解决:使用归并排序递归的排序两个子序列,当规模子序列的规模为1时每个序列为良序的序列

3.合并:合并两个已经排序的子序列产生已排序的答案

def MERGE(A, p, q, r):
    """循环不变式:A[p...r-1]为L和R中k-p个最小元素并小到大的排列,L和R为良序的原A[p...r-1]的元素
    初始化:第一次迭代之前,k=p时,A为空,循环不变式成立
    保持:若第k次迭代时,循环不变式成立,则选取R[j]或L[i]中较小的元素加入到A[k]的位置,在下次迭代开始之前循环不变式成立
    终止:r-p个元素依次加入的A[p...r-1]的位置,结束时A[p...r-1]完成排序
    """
    L = A[p:q]
    L.append(float("inf"))
    R = A[q:r]
    R.append(float("inf"))
    i = 0
    j = 0
    for k in range(p, r):
        if L[i] <= R[j]:
            A[k] = L[i]
            i = i + 1
        else:
            A[k] = R[j]
            j = j + 1

def MERGE_SORT(A, p, r):
    if p < r-1:
        q = (p + r) // 2
        MERGE_SORT(A, p, q)
        MERGE_SORT(A, q, r)
        MERGE(A, p, q, r)
分析分治算法:根据分治算法的步骤分别求每一步骤的运行时间T(n),以归并排序为例:

1.分解:分解步骤仅仅计算子数组的中间位置,需要常量时间,故D(n) = theta(1)

2.解决:递归的求解两个规模为n/2的子问题,将贡献2T(n/2)的运行时间

3.合并:一个具有n个元素的子数组合并过程需要theta(n)的时间,故C(n) = theta(n)

将以上相加则得到递归方程T(n) = 2T(n/2) + cn,使用递归树方法求解得到T(n) = theta(nlgn)


课后习题解答

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值