算法导论第二章

从今天开始会陆续更新关于算法导论的啃书相关文章,先从前往后而且比较常用的章节开始讲起,所以可能会有部分不怎么用的着的章节会跳过。由于第一章没讲什么具体内容,所以选择跳过从第二章开始讲起。

2.1插入排序

这张经典的插入排序打牌的图片也是来源于算法导论,关于插入排序的具体流程算法以及实现已经在前面进行介绍过了,所以这里不再对伪代码进行过多说明。接下来我们着重讲解一下这里的循环不变式,以及对插入排序的正确性的证明。

我们知道在插入排序的过程中,每一次新的插入之前,前面的数组总是已经有序的,所以随着插入所有的数字就完成了排序,我们一般称这种为增量算法。而我们常常用循环不变式(loop invariant)来证明一个增量算法。一个循环不变式要满足下面三个条件:

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

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

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

至于为什么循环不变式为什么是正确的可行的,本质上是一个数学归纳法。接下来我们利用这个循环不变式来证明插入排序的正确性。

1.初始化:当第一次循环的时候,数组只有一个元素,所以天然是有序的

2.保持:根据上面的伪代码可以知道,每一次循环都会把比新插入元素大的元素往后挪,从而让新插入的元素插入到一个正确的位置,使得数组有序,这也是显然的。

3.终止:由于循环终止的时候j=n+1,所以前n个元素已经变得有序,所以算法正确。

2.2分析算法

在证明了插入排序的正确性之后,我们来分析插入排序这个算法。在分析之前,必须要有一个描述所用资源及其代价的模型,在这里我们采用一种叫做RAM(random access machine)随机访问机。在RAM模型中,每一条指令都是顺序执行的,没有并发操作,并且包括常见的指令:算数指令(加减乘除,取余,取整),数据移动指令(装入、存储、复制)和控制指令(子程序调用与返回等),像上述的每一条指令我们都看作所需时间为常量。接下来开始分析插入排序:

每一行代码运行次数和代价都列在了右边,所以由上图我们可以相加得知:

最佳情况:

当最好的时候也就是数组已经有序的时候,所以里层while循环只会判断一次,并不会执行while里面的语句,也就是tj = 1。所以上述式子就会退化成下面这个式子:

最坏情况:

也就是数组逆序的时候,这是我们由tj=j,所以根据下面两个公式我们可以化简函数:

平均情况:

平均情况往往和最坏情况一样差,这里也不例外仍然是一个二次函数。

增长量级:

当我们谈论增长量级的时候,我们会忽略低阶项和高阶项的系数,因为当n的值很大的时候这些都不重要。需要注意的是这里的高阶和低阶遵循高等数学中极限中的阶数比较,所以据此我们有。关于这块的具体细节会在下一章进行讲解以及给出精确定义。

2.3设计算法

分治法:

很多有用的算法都是递归的,而这些算法都遵循分治法的思想,分治法的主体思路如下:

1.将原问题分解为若干子问题,而这些子问题是原问题的规模较小的实例

2.解决这些子问题,递归地求解各子问题。如果子问题规模足够小 ,则直接求解

3.合并这些子问题的解成原问题的解

归并排序:

分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列。

解决:使用归并排序递归地排序两个子序列。

合并:合并两个已排序的子序列以产生已排序的答案。

需要注意的是这里有一个小的技巧可以借鉴,用来简化我们的代码,由于归并排序是一个分易合难的算法,所以我们举例分析一下合的步骤。在合并的时候,由于我们不知道哪一边数组已经合并完成,所以我们在每一个数组后面都放一个无穷大,这样就不用像原来那样再用if来判断,如下图:

由于归并排序已经在之前的文章中也实现过了,所以这里具体的实现细节不再讲解,我们注重于分析其正确性。我们注重分析一下合的过程为什么正确,由于第 12~17 行的 for 循环的每次迭代时,子数组按从小到大的顺序形成有序,所以又是一个增量算法,还是用循环不变式来证明。

初始化:当循环的第一次迭代之前,字数组为空,有k=p,所以显然成立。

保持:我们为了方便理解先假设if判断中的L[i]<= R[j],这是L[i]是未被复制到A数组中的最小元素,所以当它复制到数组中之后,字数组将包含K-P+1个元素并仍然保持有序,所以依然成立。

终止:终止时K=r+1,根据循环不变式字数组A就是从小到大排完序之后的数组,除了两个哨兵位元素不录入以外,其他元素都已经复制回数组A。

分析分治算法:

分析递归式的运行时间分为两个步骤:

1.假设T(n)规模为n的一个问题的运行时间

2.假设当规模n<=c可以直接解决,否则以D(n)的代价将原问题分为a个规模为的子问题递归解决,最后以C(n)的代价进行合并,由此我们可以得到式子:

将分和合的代价带入上述式子就可得:

我们将递归树直接展开就可以求和得到总代价:

再完成每一章的讲解之后我们会发布对该章节的习题合思考题的solution,仅供大家参考,谢谢

相关推荐
算法歌者22 分钟前
[算法]入门1.矩阵转置
算法
林开落L36 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色37 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download39 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
SoraLuna1 小时前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿1 小时前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎1 小时前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
YSRM1 小时前
Experimental Analysis of Dedicated GPU in Virtual Framework using vGPU 论文分析
算法·gpu算力·vgpu·pci直通
韭菜盖饭2 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode