《算法导论》第2章 算法基础(插入排序、归并排序、复杂度计算)

(最近在自己学习《算法导论》一本书,之前本来喜欢手写笔记,但是随即发现自己总是把笔记弄丢,所以打算做一个电子版的笔记)

(另外书中用的都是伪代码,笔记中如果需要尝试的地方都是python代码)

2.1 插入排序

        基本思想:将待排序的数列看成两个部分(以从小到大为例),前一半是排序完成的,后一半是乱序的,对于乱序的第一个,开始和前一半里最大的数字、第二大的数字……依次比较,等到合适的位置就将它放进去。然后比对过的数字向后移动一位,相应的排序完成的长度加一,没有排序的减一。如:

5 | 2 6 4 3 1 ➡️   2 ? 5: 2 < 5!                                                      把2插到5前面

2 5 | 6 4 3 1 ➡️   6 ? 5: 6 > 5!                                                      把6插到5后面

2 5 6 | 4 3 1 ➡️   4 ? 6: 4 < 6!  4 ? 5:  4 < 5!  2 ? 4: 2<4!       把4插到2和5之间 

……

        另外在证明插入排序的这个部分用到了循环不变式

        初始化:循环的第一次迭代之前,命题为真;

        保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍然为真;

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

        在插入排序之中,初始化性质体现在一开始分成两个部分的时候,第一个部分由于长度是1,所以也是必然满足所谓的“从小到大”排列的。而保持的性质则体现在如果将一个新的数字插入到序列中,例如把4插入的那一步,插入之前和之后仍然满足第一个序列按照“从小到大”排列。而终止性质则体现在排序的过程结束的时候,第一个序列是“从小到大”排列的,是“有用的性质”,可以证明这个算法是正确的。

2.2 分析算法

        分析算法的效率,采用的假想中的一种通用的单处理器计算模型——随机访问机(random-access machine,RAM)来实现,它包含着算数指令(加减乘除求余取整),数据移动(装入、存储、复制)和控制指令(条件与无条件转移、子程序调用和返回),每条指令的时间都是常数,有助于我们评价一个算法的时间效率。

        由于是理想模型,不会考虑实际计算中计算机的运行速度、高速缓存、虚拟内存等等

        通过分析算法复杂度,可以给出最坏情况下的运行时间,尽管很多时候时间和数据规模的函数并不是一个简单的基本初等函数乘以一个系数,但是只需要考虑它在趋向于较大的数据规模时主要控制运行时间的一项。也就是高次项。相比于考虑它的值,考虑它的变化率往往是更加有意义的。换句话说,我们真正感兴趣的是运行时间的增长率增长量级

        *为什么往往考虑最坏情况?

        -可以对算法的运行状态进行有效的表征(例如超过最坏情况仍没有看到结果,可能bug了)

        -很多时候最坏情况往往是平均情况,例如数据库查找时,很多时候找不到所需要的数据,但是算法很有可能将整个数据库遍历了一遍,也就是最坏情况。

2.3 设计算法

        利用“分治法”的设计方法,可以设计出一种比插入排序更快的方法:归并排序(递归)

        基本思想:如果有两个有序的列表片段,和两个位置指针,通过比较位置指针指向的值的大小,依次插入,最终得到的大列表片段也是有序的。而且又很明显这两个列表片段长度是1的时候,列表显然是有序的。

自己尝试的python代码:

# 算法导论2.3 归并排序练习代码
def guibing(List, p, q, r):
    '''
    递归排序函数
    :param List: 等待排序的列表
    :param p: 等待排序的列表片段的下界
    :param q: 等待排序的列表片段的中间值
    :param r: 等待排序的列表片段的上界
    :return: 排序好的List
    '''
    if p == q or q == r:
        return List
    ListL = guibing(List, p, (p + q) // 2, q)[p:q]
    ListR = guibing(List, q, (q + r) // 2, r)[q:r]
    print(ListL, ListR)
    ListL.append(1000)
    ListR.append(1000)
    L, R = 0, 0
    list_return = []
    while L < q - p or R < r - q:
        if ListL[L] < ListR[R]:
            list_return.append(ListL[L])
            L += 1
        else:
            list_return.append(ListR[R])
            R += 1
    list_return = List[:p] + list_return + List[r:]
    print(list_return)
    print('-'*80)
    return list_return


guibing([2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34], 1, 8, 15)

        输出:

[5] [3]
[2, 42, 3, 5, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[42] [3, 5]
[2, 3, 5, 42, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[44] [53]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[4] [7]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[44, 53] [4, 7]
[2, 42, 5, 3, 4, 7, 44, 53, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[3, 5, 42] [4, 7, 44, 53]
[2, 3, 4, 5, 7, 42, 44, 53, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[4] [83]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[9] [4, 83]
[2, 42, 5, 3, 44, 53, 4, 7, 4, 9, 83, 56, 25, 56, 34]
--------------------------------------------------------------------------------
[56] [25]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 25, 56, 56, 34]
--------------------------------------------------------------------------------
[56] [34]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 56, 25, 34, 56]
--------------------------------------------------------------------------------
[25, 56] [34, 56]
[2, 42, 5, 3, 44, 53, 4, 7, 9, 4, 83, 25, 34, 56, 56]
--------------------------------------------------------------------------------
[4, 9, 83] [25, 34, 56, 56]
[2, 42, 5, 3, 44, 53, 4, 7, 4, 9, 25, 34, 56, 56, 83]
--------------------------------------------------------------------------------
[3, 4, 5, 7, 42, 44, 53] [4, 9, 25, 34, 56, 56, 83]
[2, 3, 4, 4, 5, 7, 9, 25, 34, 42, 44, 53, 56, 56, 83]
--------------------------------------------------------------------------------

进程已结束,退出代码为 0

        很容易看到列表长度的变化,和按次序插入的过程。

        分治法解决问题的步骤:

        分解:分解原问题成若干个子问题

        解决:求解子问题,如果子问题的规模足够小就直接求解

        合并:将子问题合并成原问题的解。

        通过这样的递归方法,我们可以看到,一个排序问题会被转化为2个规模是原来一半大小的排序问题,最终按照指数形式被分解成很多个规模为1的问题。很容易猜到算法的复杂度应该是一个和对数相关的算式。事实上是O(nlgn)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值