最近在学习陈斌老师的数据结构与算法课程,打算将课程中的重要知识点总结在博客里,和大家一起分享。这是我第一次学习这方面的知识,如果有错误的地方,请一定指要出来呀,谢谢~
我们在对算法进行分析时,主要从计算资源消耗的角度来评判和比较算法。计算资源指标一般有两种,一种是算法解决问题过程中需要的存储空间或内存,由于存储空间受到问题自身数据规模的变化影响,要区分哪些存储空间是问题本身描述所需,哪些是算法占用,不容易。因此我们采用算法的执行时间来衡量计算资源。如果使用算法运行时间的实际检测来度量,会发现同一个算法,采用不同的编程语言编写,放在不同的机器上运行,得到的运行时间会不一样。我们需要更好的方法来衡量算法运行时间,这个指标与具体的机器、程序、运行时段都无关,即算法时间复杂度。
1 算法时间度量指标
- 一个算法所实施的操作数量或步骤数可作为独立于具体程序/机器的度量指标。
- 需要一种通用的基本操作来作为运行步骤的计量单位,这个通用的基本操作我们选择为赋值语句。
- 因此赋值语句执行次数可以作为算法时间度量的指标。
- 问题规模是影响算法执行时间的主要因素,在下面例1这个算法中,需要累计的整数个数合适作为问题规模的指标(前100,000个整数求和对比前1,000个整数求和,算是同一问题的更大规模)。
- 算法分析的目标是要找出问题规模会怎么影响一个算法的执行时间。
:对于“问题规模”n,赋值语句数量为T(n)=1+n。
2 数量级函数(大O表示法)
- 基本操作数量函数T(n)的精确值并不是特别重要,重要的是T(n)中起决定性因素的主导部分(当问题规模增大的时候,T(n)中的一些部分会盖过其它部分的贡献)。
- 数量级函数描述了T(n)中随着n增加而增加速度最快的主导部分,称作大O表示法,记作O(f(n)),其中f(n)表示T(n)中的主导部分。
- 常见的大O数量级函数:
:
1.对于T(n)=1+n,当n增大时,常数1显得越来越不重要,所以可以去掉1,保留n作为主要部分,运行时间数量级就是O(n)。
2.对于 ,当n越来越大, 项就越来越重要,其它两项对结果的影响则越来越小,运行时间数量级为 。
3.从代码分析确定执行时间数量级函数:以下代码赋值语句可以分为4个部分,根据下图分析, ,仅保留最高阶项 ,去掉所有系数,数量级为 。
3 从“变位词”判断问题看算法时间复杂度
- 问题描述:所谓“变位词”是指两个词之间存在组成字母的重新排列关系,如:heart和earth,python和typhon。
- 解题目标:写一个bool函数,以两个词作为参数,返回这两个词是否变位词。
思路:
- 将词1中的字符逐个到词2中检查是否存在
- 存在就“打勾”标记(防止重复检查)
- 如果每个字符都能找到,则两个词是变位词
- 只要有1个字符找不到,就不是变位词
- 如下图:
程序代码:
算法分析:
- 问题规模:词中包含的字符个数n
- 主要部分在于两重循环(外层循环遍历s1每个字符,将内层循环执行n次;而内层循环在s2中查找字符,每个字符的对比次数,分别是1、2…n中的一个,而且各不相同)
- 所以总执行次数是1+2+3+……+n,数量级为O(n2)
思路:
- 将两个字符串都按照字母顺序排好序
- 再逐个字符对比是否相同,如果相同则是变位词
- 有任何不同就不是变位词
- 如下图:
程序代码:
算法分析:
- 问题规模:词中包含的字符个数n
- 粗看上去,本算法只有一个循环,最多执行n次,数量级是O(n)
- 但循环前面的两个sort并不是无代价的,排序算法采用不同的解决方案,其运行时间数量级差不多是O(n2)或者O(n log n),大过循环的O(n)
- 所以本算法时间主导的步骤是排序步骤,运行时间数量级就等于排序过程的数量级O(n log n)
思路:
- 对比两个词中每个字母出现的次数
- 如果26个字母出现的次数都相同,这两个字符串就一定是变位词
程序代码:
算法分析:
- 问题规模:词中包含的字符个数n
- 计数比较算法中有3个循环迭代,但不像解法1那样存在嵌套循坏
- 前两个循环用于对字符串进行计数,操作次数等于字符串长度n;第3个循环用于计数器比较,操作次数总是26次
- 所以总操作次数T(n)=2n+26,其数量级为O(n)
- 但是本算法依赖于两个长度为26的计数器列表,来保存字符计数,这相比前2个算法需要更多的存储空间,如果考虑由大字符集构成的词(如中文具有上万不同字符),还会需要更多存储空间。牺牲存储空间来换取运行时间,或者相反,这种在时间空间之间的取舍和权衡,在选择问题解法的过程中经常会出现。
4 Python两种内置数据类型上各种操作的大O数量级
4.1 List列表数据类型
最常用的
- 按索引取值和赋值(v=a[i], a[i]= v),由于列表的随机访问特性,这两个操作执行时间与列表大小无关,均为O(1)。
- 另一个是列表增长,可以选择append()和__add__()“+”,lst.append(v)执行时间是O(1),lst= lst+ [v]执行时间是O(n+k),其中k是被加的列表长度。
List基本操作的大O数量级
- pop()从列表末尾移除元素,O(1),而pop(i)从列表中部移除元素,O(n)
- 原因:从中部移除元素的话,要把移除元素后面的元素全部向前挪位复制一遍
4.2 dict数据类型
字典与列表不同,根据关键码(key)找到数据项,而列表是根据位置(index)
最常用的
- 最常用的取值get和赋值set,其性能为O(1)
- 另一个重要操作contains(in)是判断字典中是否存在某个关键码(key),这个性能也是O(1)
dict基本操作的大O数量级
4.3 更多Python数据类型操作复杂度
参考Python官方的算法复杂度网站:https://wiki.python.org/moin/TimeComplexity