本系列读书笔记参考自数据结构与算法经典问题解析第二版内容,习题答案部分有误,已勘正,部分严格证明参考算法导论第三版,水平有限,如有纰漏,尽请指出
基础概念
数据结构是计算机存储和组织数据的一种特定方式,通过特定的数据结构可以使相应的数据处理更加有效,常见的数据结构包括:数组,文件,链表,栈,队列,树,图等。
根据其元素组织方式,数据结构可分为两种类型:
- 线性数据结构:可以按线性次序访问元素,但它并不强制将所有元素连续存储在一起(如链表)。主要有链表,栈和队列
- 非线性数据结构:这种数据结构的元素是以非线性次序来存储和访问元素的,例如树和图
为了简化求解问题的过程,我们将数据结构和相关运算组合起来,统称为抽象数据类型ADT,其包含两个部分:数据的声明和运算的声明
常用的ADT包括:链表,栈,队列,优先队列,二叉树,字典,并查集,散列表,图等其他类型
当定义ADT时,不需要考虑其实现细节,仅当需要使用它们时,才考虑具体的实现。不同的ADT类型适用于不同的场合,需要我们灵活应用
算法分析的目标在于根据运行时间及其它的一些因素(如内存,开发者的工作量等)来比较算法的优劣
运行时间分析是指当问题的输入规模增大时,研究问题的处理时间是如何增加的
为了比较算法,以下是几种客观评价指标:
- 执行时间:不同的计算机会呈现出较大差异,不是一个好的评价标准
- 执行的语句数:因为执行的语句和编程语言以及程序员个人的编程风格相关,也不是一个好的评价标准
- 函数表示:假设用一个函数表示一个算法的运行时间,而问题的输入规模用表示,通过比较不同算法对应的的,就足以比较出各种算法的优劣
增长率是指随着输入规模的增加,算法运行时间增加的速度,也就是说对于一个给定的函数,我们会忽略相对影响更小的低阶因变量(当n很大时),例如对于下式我们可以认为:
算法分析
算法分析有如下三种类型:
- 最坏情况:定义算法最长运行时间的输入,这种输入使得算法运行最慢
- 最好情况:定义算法最短运行时间的输入,这种输入使得算法运行最快
- 平均情况:提供算法运行时间的预测值,此时假设输入是随机的,也就是说有:
在我们定义了输入的不同类型后,我们需要对算法运行时间的上下界和平均时间做一个准确的定义
大O表示法
大O表示法给出了函数的严格上界,我们记作,表示当输入规模很大时,的渐进上界是
用更准确的数学语言来说,大O表示法定义为:
注意当边界只能大于(小于)而不能等于时,认为边界是渐进的但不是紧确的,也就是满足下式
此时我们称是的一个渐进非紧确上界
显然是指所有与具有相同增长率或比起增长率小的函数的集合,例如下图
但使用小o表示法,则不包含例如内的函数
表示法
与大O表示法类似,表示法给出的严格下界,记作,表示输入规模很大时,的渐进下界是
用更准确的数学语言来说,表示法定义为:
在使用中,我们需要尽可能求出满足条件的最大阶的
同样的当满足下式
此时我们称是的一个渐进非紧确下界
例如,则是正确的,而也是正确的,是错误的
表示法
表示法决定了算法的时间增长率上界和下界是否相同,如果大O表示法与表示法给出的结果是一样的,那么表示法也会给出相同的结果。而对于一个给定的函数,如果它增长率的上界和下界是不同的,那么表示法需要通过分析所有可能的时间复杂度,然后得出平均情况下的结论(例如快速排序的平均情况,参见第十章)
表示法的数学含义:
- 对于函数,如果存在常数使得,那么有
用图示表示以上表示法如下:
小结:
一些通用的计算规则:
渐进表示法的基本性质:
一些常用的数学公式:
分治法定理
分治法是指把一个问题划分为多个子问题,每个子问题是原问题的一部分,然后执行一些额外的工作来计算最后的答案,例如归并排序中,将原问题分为两个子问题,每个子问题都是原问题规模的一半,然后用的额外工作完成归并,可以用下式表示
我们把这种分治法思想推广,如果一个问题可以通过以下递归形式表示
则有如下几种情况:
- 当时,
- 当时
a. 如果,
b. 如果,
c. 如果, - 如果
a. 如果,
b. 如果,
这就是分治法的主定理,下面是一些使用主定理的例子
在主定理之上,还有一些变形:
变形1:当对于常数和有
如果的时间复杂度是,则
变形2:,则其复杂度为
猜测的方法
平摊分析
下面是一些典型习题及其解答
上图中F(2)还需要分为F(1)和F(0),树有N层
解答有误,应该是假设是一颗满二叉树,则叶子节点少于(事实上,依然属于阶)
时间复杂度:
空间复杂度:
也就是说
上式中注意省略掉这一步