《算法导论》第二章笔记

《算法导论》第二章笔记

前言

表示终于有幸能一睹《算法导论》这本算法神作了。

虽然之前也或多或少接触过算法,比如研究HashMap等数据结构等。

看过我之前博客的小伙伴,应该可以看到我之前写过排序算法和查找算法等(C语言版本)。

不过我更希望是像大学学习《运筹学》那样,系统地整理算法体系。所以就有这次的博客,针对《算法导论》的学习笔记。我会将学习《算法导论》过程中遇到的一些重点,记录下来。

另外,每章的实现代码(Java版),我都会另开一篇博客,进行记录。

摘要

1.循环不变式

2.循环不变式的性质:

* 初始化:循环的第一次迭代之前,它为真
* 保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真
* 终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的

PS:书中,这里就插入排序,分别阐述了该三条性质

PS:联系数学归纳法,初始化 针对是数学归纳法中的n = 1这样的初始条件证明,而 保持 针对的是数学归纳法中 n=m成立=》n=m+1成立 的递归条件证明。至于 终止 这是区分现实(程序)与理想(数学)的分界线,毕竟现实中程序的执行是存在终点的。

3.书中p11中约定了诸多伪代码规则,如指针,值传递等

4.算法的分析是采用一种通用的单处理器计算模型——RAM(random-access machine)模型作为实现技术

5.RAM模型包含真实计算机中的常见指令:

* 算数指令(加减乘除,取余,上下取整)
* 数据移动指令(装入,存储,复制)
* 控制指令(条件与无条件转移,子程序调用与返回)

PS:并且设置所有指令的耗时是常量的。实际情况并非如此,但是过于纠结细节,造成计算模型过于复杂,是不必要的。

6.算法的耗时往往依赖于输入,所以通常把一个程序的运行时间描述成其输入规模的函数。

7.输入规模往往依赖于研究的问题。并且其参数列表也依赖于实际问题,如图类算法,往往涉及图中的顶点数和边数输入。

8.一个算法在特定输入上的运行时间是指执行的基本操作数或步数。这里同样假定执行每行代码需要常量时间(格式化,规范的代码,而不是压缩代码)。

9.最坏情况与平均情况分析:往往集中求解最坏运行时间,即对规模为n的任何输入,算法的最长运行时间。这样做的三点理由:

* 一个算法的最坏情况运行时间给出了任何输入的运行时间的一个上界
* 对某些算法,最坏情况经常出现
* “平均情况”往往与最坏情况大致一样差

PS:上述第三条的理解:“平均情况”的增长量级(时间复杂度)往往和最坏情况一致

PS:特定情况下,我们会对一个算法的平均情况运行时间感兴趣,如概率分析

10.增长量级:对于输入规模n,在n足够大时,影响增长量级的是公式中最重要的项(即多项式中幂最大的项)

11.增量方法作为算法设计的技术之一,插入排序使用了增量方法

12.许多算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关地若干问题。

PS:递归令人想起数学归纳法中的 n=m成立=》n=m+1成立的过程

PS:程序中不断重复一个过程,那么实现无非就是循环与递归。如果重复次数直接,明确,可以采用循环。如果重复次数不明确,甚至需要过程不断重复后才知道,则采用递归。

13.递归算法增寻分治法地思想:将原问题分解为几个规模较小但类似原问题的子问题,递归地求解这些子问题,然后在合并这些子问题的解来建立原问题的解。

14.分治模式在每层递归时有三个步骤:

* 分解:将问题分解为若干更小的子问题
* 解决:将对应子问题进行求解
* 合并:将这些子问题的解合并,并传给上一层递归

15.哨兵:为避免每个基本步骤都要检查是否有堆为空。故在每个堆的底部防止一个哨兵,它包含一个特殊值,用于简化代码。

PS:如在归并排序中,我通过Integer.MAX_VALUE,来避免为空的判断。

16.递归式(长得就像分段函数一样。只不过分为初始化与递归两个阶段)

17.递归树

18.归并排序中,完全扩展的递归树具有lgn+1层,每层将贡献总耗时cn,故总代价为cnlgn+cn

练习

练习部分,所以代码实现,都会放在对应的代码实现博客。剩下的练习,我只会挑选一些进行记录(有些东东,手写还好。用md写,太麻烦了)。当然,如果有小伙伴有疑惑,可以私信或@我

2.3-6

不可以。

虽然上层遍历元素的时间复杂度为n,而下层采用二分查找(时间复杂度为lgn),看起来整体时间复杂度为nlgn。但是下层并不仅仅是二分查找,查找只是定位到了目标位置,还需要进行当前元素的插入动作。而无论是在数组,还是别的顺序表中,插入的时间复杂度为n。所以就变成了n*(n+lgn),时间复杂度就变成了n^2。

综上,无法通过二分查找,将插入排序的时间复杂度从n^2优化到nlgn。

2.3-7

首先,通过归并排序,对数组进行排序(因为后面用到的二分查找需要顺序表。并且题目只要求确认是否存在,并没有要求返回对应index,所以在一开始进行排序操作)。

外层通过循环,依次获得对应元素,根据branchTarget = target - array[i]这样的操作获得两个元素中的另一个元素。再通过二分查找,判断集合中是否存在对应的另一个元素。

归并排序的时间复杂度为nlgn,循环遍历的时间复杂度为n,二分查找的时间复杂度为lgn,所以最终的耗时为nlgn+n*lgn,故最终时间复杂度为nlgn。

具体实现,请看本章代码实现博客。

思考

由于该章的思考题,都需要进行公式计算与展示,而我并不会用MarkDown表示,所以就不写出来了。如果感兴趣,给各位一个资料(Algorithems Solutions)。

总结

其实这个章节还是比较简单的。算法上面举了一些有关排序的例子,以及一个折半查找的demo。数学方面,一方面需要还记得高中的数学归纳法,另一方面需要一定的排列组合的认识(进行复杂度的计算表)。

附录

参考

  1. 《算法导论》
  2. Algorithems Solutions

猜你喜欢

转载自www.cnblogs.com/Tiancheng-Duan/p/12343865.html