python做算法分析_学习笔记:[算法分析]数据结构与算法Python版

什么是算法分析

对比程序,还是算法?

❖如何对比两个程序?

看起来不同,但解决同一个问题的程序,哪个“ 更好”?

❖程序和算法的区别

算法是对问题解决的分步描述 程序则是采用某种编程语言实现的算法,同一个 算法通过不同的程序员采用不同的编程语言,能 产生很多程序

大O表示法

算法时间度量指标

❖ 一个算法所实施的操作数量或步骤数可作为 独立于具体程序/机器的度量指标 哪种操作跟算法的具体实现无关? 需要一种通用的基本操作来作为运行步骤的计量单位

❖ 赋值语句是一个合适的选择

一条赋值语句同时包含了(表达式)计算和(变量) 存储两个基本资源

仔细观察程序设计语言特性,除了与计算资源无关的 定义语句外,主要就是三种控制流语句和赋值语句, 而控制流仅仅起了组织语句的作用,并不实施处理。

❖分析SumOfN的赋值语句执行次数

对于“问题规模”n

赋值语句数量T(n)=1+n 那么,什么是问题规模?

967964-20200229233533957-212784.png

❖问题规模:影响算法执行时间的主要因素

❖在前n个整数累计求和的算法中,需要累 计的整数个数合适作为问题规模的指标 前100,000个整数求和对比前1,000个整数求和 ,算是同一问题的更大规模

❖算法分析的目标是要找出问题规模会怎么影响一个算法的执行时间

数量级函数 Order of Magnitude

❖基本操作数量函数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)

影响算法运行时间的其它因素

❖有时决定运行时间的不仅是问题规模

❖某些具体数据也会影响算法运行时间

分为最好、最差和平均情况,平均状况体现了算 法的主流性能 对算法的分析要看主流,而不被某几种特定的运行状况所迷惑

常见的大O数量级函数

❖通常当n较小时,难以确定其数量级

❖当n增长到较大时,容易看出其主要变化 量级

967964-20200229233151849-596703321.png

967964-20200229233210926-1499879543.png

从代码分析确定执行时间数量级函数

可以看到38--40这一段代码有3个赋值符号所以记作3

41-45行代码有双重循环并且有3个n 记作3n2

46-48行代码有单个循环2个赋值记作2n

49行最后一行代码又一个赋值符号记作1

最后得出结果:

967964-20200229232356640-1282698428.png

❖仅保留最高阶项n2 ,去掉所有系数

❖数量级为O(n2)

967964-20200229232801920-318482710.png

其它算法复杂度表示法

❖大O表示法 表示了所有上限中最小的那个上限。

❖大𝛀表示法 表示了所有下限中最大的那个下限

967964-20200229233017040-1054142695.png

❖大𝚹表示法 如果上下限相同,那么就可以用大𝚹𝚹表示

967964-20200229233041176-1970286377.png

“变位词”判断问题

“变位词”判断问题

所谓“变位词”是指两个词之间存在组成字母的 重新排列关系 如:heart和earth,python和typhon 为了简单起见,假设参与判断的两个词仅由小写 字母构成,而且长度相等

❖解题目标:写一个bool函数,以两个词作 为参数,返回这两个词是否变位词

❖可以很好展示同一问题的不同数量级算法

解法1:逐字检查

❖解法思路 将词1中的字符逐个到词2中检查是否存在 存在就“打勾”标记(防止重复检查) 如果每个字符都能找到,则两个词是变位词 只要有1个字符找不到,就不是变位词

967964-20200229073356013-1575271907.png

❖程序技巧 实现“打勾”标记:将词2对应字符设为None 由于字符串是不可变类型,需要先复制到列表中

967964-20200229073445409-1800473267.png

❖逐字检查法代码

ContractedBlock.gif

ExpandedBlockStart.gif

#逐字检查法

defanagramSolution1(s1,s2):

alist=list(s2) #将s2内容转换成列表复制到alist中

pos1=0;

stillOK=Truewhile pos1

pos2=0

found=Falsewhile pos2

if s1[pos1]==alist[pos2]: #s1与s2的值进行对比 如果为真则判断整确跳出这个循环

found=Trueelse:

pos2=pos2+1 #否则继续比较

iffound:

alist[pos2]=None #找到打勾

else:

stillOK=False #未找到、失败

pos1=pos1+1

returnstillOKprint(anagramSolution1('python','ypthon'));

View Code

❖算法分析

问题规模:词中包含的字符个数n

主要部分在于两重循环 外层循环遍历s1每个字符,将内层循环执行n次 而内层循环在s2中查找字符,每个字符的对比次 数,分别是1、2…n中的一个,而且各不相同

所以总执行次数是1+2+3+……+n 可知其数量级为O(n2)

967964-20200229073757557-480021290.png

解法2:排序比较

❖解题思路 将两个字符串都按照字母顺序排好序 再逐个字符对比是否相同,如果相同则是变位词 有任何不同就不是变位词

967964-20200229074044511-1761075182.png

❖排序比较代码

ContractedBlock.gif

ExpandedBlockStart.gif

defanagramSolution2(s1,s2):#分别转换为列表赋值给alist1、alist2

alist1=list(s1)

alist2=list(s2)#分别排序

alist1.sort()

alist2.sort()

pos=0

matches=Truewhile pos

pos=pos+1

else:

matches=Falsereturnmatchesprint(anagramSolution2('notyph','ypthon'));

View Code

❖算法分析

粗看上去,本算法只有一个循环,最多执 行n次,数量级是O(n)

但循环前面的两个sort并不是无代价的 如果查询下后面的章节,会发现排序算法采用不 同的解决方案,其运行时间数量级差不多是 O(n2)或者O(n log n),大过循环的O(n)

所以本算法时间主导的步骤是排序步骤

本算法的运行时间数量级就等于排序过程 的数量级O(n log n)

解法3:暴力法

暴力法解题思路为:穷尽所有可能组合

将s1中出现的字符进行全排列,再查看s2 是否出现在全排列列表中

这里最大困难是产生s1所有字符的全排列 根据组合数学的结论,如果n个字符进行全排列 ,其所有可能的字符串个数为n

967964-20200229075510148-989518415.png

我们已知 n! 的增长速度甚至超过2n 例如,对于20个字符长的词来说,将产生 20!=2,432,902,008,176,640,000个候选词 如果每微秒处理1个候选词的话,需要近8万年时 间来做完所有的匹配。

❖结论:暴力法恐怕不能算是个好算法

解法4:计数比较

解题思路:对比两个词中每个字母出现的 次数,如果26个字母出现的次数都相同的 话,这两个字符串就一定是变位词

❖具体做法:为每个词设置一个26位的计数 器,先检查每个词,在计数器中设定好每 个字母出现的次数

❖计数完成后,进入比较阶段,看两个字符 串的计数器是否相同,如果相同则输出是 变位词的结论

❖计数比较代码

ContractedBlock.gif

ExpandedBlockStart.gif

defanagramSolution4(s1,s2):#分别转换为列表赋值给alist1、alist2

c1=[0]*26;

c2=[0]*26;#1.利用ascii特性a=65 b=66... z=90 所以开辟26个字母开辟26个空间#2.首先循环判断s1的每一个字母 用当前字母的ascii码减去a的ascii码#3.将得到的差当成数组下标给c1并赋值1让它里面不为None#4.s2循环跟s1一样步骤 参考2 3#5.循环判断 c1和c2数组所有的内容 如果有一处不相等则不相等

for i in range(len(s1)): #分别计数

pos=ord(s1[i])-ord('a')

c1[pos]=1

for i inrange(len(s2)):

pos=ord(s2[i])-ord('a')

c2[pos]=1j=0

stillOK=Truewhile j<26 and stillOK: #计数器比较

if c1[j]==c2[j]:

j=j+1

else:

stillOK=FalsereturnstillOKprint(anagramSolution4('cdge','egdc'));

View Code

❖算法分析

❖计数比较算法中有3个循环迭代,但不象 解法1那样存在嵌套循坏 前两个循环用于对字符串进行计数,操作次数等 于字符串长度n 第3个循环用于计数器比较,操作次数总是26次

❖所以总操作次数T(n)=4n+26,其数量级 为O(n) 这是一个线性数量级的算法,是4个变位词判断 算法中性能最优的

❖值得注意的是,本算法依赖于两个长度为 26的计数器列表,来保存字符计数,这相 比前3个算法需要更多的存储空间 如果考虑由大字符集构成的词(如中文具有上万 不同字符),还会需要更多存储空间。

❖牺牲存储空间来换取运行时间,或者相反 ,这种在时间空间之间的取舍和权衡,在 选择问题解法的过程中经常会出现。

Python数据类型的性能

1.Python两种内置数据类型

各种操作的大O数量级

列表list和字典dict 这是两种重要的Python数据类型,后面的课程 会用来实现各种数据结构 通过运行试验来估计其各种操作运行时间数量级

对比list和dict的操作

967964-20200229180919991-806317367.png

2.List列表数据类型

常用操作性能

❖最常用的是:按索引取值和赋值(v = a[i], a[i]= v) 由于列表的随机访问特性,这两个操作执行时间 与列表大小无关,均为O(1)

❖另一个是列表增长,可以选择append() 和__add__()“+” lst.append(v),执行时间是O(1) lst= lst+ [v],执行时间是O(n+k),其中k是 被加的列表长度 选择哪个方法来操作列表,决定了程序的性能

❖list类型各种操作(interface)的实现方 法有很多,如何选择具体哪种实现方法?

❖总的方案就是,让最常用的操作性能最好 ,牺牲不太常用的操作

80/20准则:80%的功能其使用率只有20%

967964-20200229182135162-2125955638.png

4种生成前n个整数列表的方法

❖ 首先是循环连接列 表(+)方式生成

deftest1():

l=[]for i in range(1000):

l=l+[i]returnlprint(test1())

❖ 然后是用append 方法添加元素生成

deftest2():

l=[]for i in range(1000):

l.append(i)returnlprint(test2())

❖ 接着用列表推导式 来做

deftest3():

l=[i for i in range(1000)]returnlprint(test3())

❖ 最后是range函数 调用转成列表

deftest4():

l=list(range(1000))returnlprint(test4())

使用time对函数进行计时

整体代码

ContractedBlock.gif

ExpandedBlockStart.gif

from timeit importTimerdeftest1():

l=[]for i in range(1000):

l=l+[i]deftest2():

l=[]for i in range(1000):

l.append(i)deftest3():

l=[i for i in range(1000)]deftest4():

l=list(range(1000))

t1=Timer("test1()","from __main__ import test1")print ("concat %f seconds\n" %t1.timeit(number=1000))

t2=Timer("test2()","from __main__ import test2")print("append %f seconds\n" % t2.timeit(number=1000))

t3=Timer("test3()","from __main__ import test3")print("comprehension %f seconds\n" % t3.timeit(number=1000))

t4=Timer("test4()","from __main__ import test4")print("list range %f seconds\n" % t4.timeit(number=1000))

View Code

❖我们看到,4种方法运行时间差别很大

列表连接(concat)最慢,List range最快, 速度相差近200倍。

append也要比concat快得多 另外,我们注意到列表推导式速度是append两 倍的样子

967964-20200229183335910-1587323491.png

List基本操作的大O数量级

967964-20200229183626976-666239937.png

list.pop的计时试验

❖我们注意到pop这个操作

pop()从列表末尾移除元素,O(1)

pop(i)从列表中部移除元素,O(n)

❖原因在于Python所选择的实现方法

从中部移除元素的话,要把移除元素后面的元素 全部向前挪位复制一遍,这个看起来有点笨拙 但这种实现方法能够保证列表按索引取值和赋值 的操作很快,达到O(1) 这也算是一种对常用和不常用操作的折衷方案

3.dict数据类型

❖字典与列表不同,根据关键码(key)找 到数据项,而列表是根据位置(index)

最常用的取值get和赋值set,其性能为O(1) 另一个重要操作contains(in)是判断字典中是 否存在某个关键码(key),这个性能也是O(1)

967964-20200229184512384-1138777073.png

Python官方的算法复杂度网站: https://wiki.python.org/moin/TimeCompl exity

“变位词”判断问题

1

“变位词”判断问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值