算法导论第二章

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

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,仅供大家参考,谢谢

相关推荐
Swift社区2 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman3 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年3 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Dong雨4 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
SoraLuna4 小时前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
liujjjiyun4 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥4 小时前
c++中mystring运算符重载
开发语言·c++·算法
trueEve5 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
天若有情6735 小时前
c++框架设计展示---提高开发效率!
java·c++·算法
ahadee5 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯