1.什么是算法分析?
如何对比两个程序?
- 好的程序变量名清晰,无用垃圾代码少。
- 代码风格,可读性
程序和算法的区别
- 算法是对问题解决的分步描述
- 程序则是采用某种编程语言实现的算法
算法分析的概念
- 算法分析主要就是从计算资源消耗的角度来评判和比较算法。更高效利用计算资源,或者更少占用计算资源的算法,就是好算法。
那么何为计算资源?
- 一种是算法解决问题过程中需要的存储空间或内存
- 另一种是算法的执行时间
运行时间检测
- 获取打印111所需时间
import time
start = time.time()
print(111)
end = time.time()
running_time = end-start
print('time cost : %.5f sec' %running_time)
运行结果
111
time cost : 0.00000 sec
- 显示时间是0.00000秒,是因为打印111所需要的时间太短了,所以不是很精确。
如何比较算法的“好坏”?
- 衡量算法可从时间空间两个角度分析
有两个不同的算法,都可以计算前n项数的和。
①迭代算法
import time
def sumOfN2(n) :
start = time. time( )
theSum = 0
for i in range(1,n+1):
theSum = theSum + i
end = time. time( )
return theSum, end-start
print("Sum is %d required %10.7f seconds"% sumOfN2 (10000) )
sum is 50005000 requires 0.0007980 seconds
②无迭代算法
import time
def sumOfN3(n) :
start = time. time( )
theSum=(n*(n+- 1))/2
end =time. time( )
return theSum ,end-start
sum is 50005000 requires 0.0000010 seconds
第二种算法所需时间更短
观察一下第一种迭代算法
- 包含了一个循环,可能会执行更多语句 这个循环运行次数跟累加值n有关系,n增加,循环次数也增加,执行时间增加
import time
def sumOfN2(n) :
theSum = 0
for i in range(1,n+1):#就是这条循环语句!!
theSum = theSum + i
return theSum
2.大O表示法
赋值语句 是作为算法时间的度量指标
- 赋值语句执行少,则程序运行时间短。
如何来看赋值语句执行的次数呢?
- 在这个程序中,我们用T(n)来表示赋值语句的条数
def sumOfN2(n) :
theSum = 0 #一条赋值语句,T(n)1=1
for i in range(1,n+1):#一条循环语句,循环了n次T(n)2=n
theSum = theSum +i#一条赋值语句,T(n)2=n*1
return theSum
所以对于"问题规模"n,赋值语句数量
T(n)=T(n)1+T(n)2=n+1
什么是问题规模
- 问题规模就是影响算法执行时间最主要的因素。
- 所以对于上面那个程序,前1000个整数求和,对比前100个整数求和,算是较大的问题规模,所以前n个整数累计求和,n就是问题规模了
算法分析目标?
- 算法分析的目标是找出问题规模会怎样影响算法执行时间
数量级函数
- 数量级函数就是用来描述T(n)中随着n增加增加速度最快的主导部分,称作“大O”表示法,记作O(f(n)),其中f(n)表示T(n)中的主导部分
主导部分怎么确定呢?就是T(n)中增长速度最快的部分
T(n)=n+1,主导部分是n
T(n)=200n3+20n+1000,主导部分是n3
影响算法运行时间的因素不只有问题规模,还有其他。
常见的大O数量级函数
f(n) | 名称 |
---|---|
1 | 常数 |
log(n) | 对数 |
n | 线性 |
n*log(n) | 对数线性 |
n2 | 平方 |
n3 | 立方 |
2n | 指数 |
从代码分析执行时间数量级函数
a=5
b=6
c = 10 #3条赋值语句,所以T(n)1=3
for i in range(n): #循环语句n
for j in range(n): #循环语句n,所以T(n)2=n的平方
X=i*i
Y=j*j
Z=i*1 #三条赋值语句,所以T(n)2=3n的平方
for k in range(n):#循环语句n,T(n)3=n
W =a*k+45
V=b*b #两条赋值语句,所以T(n)3=2n
d=33 #1条赋值语句,所以T(n)4=1
所以,T(n)=T(n)1+T(n)2+T(n)3+T(n)4=3+3n2+2n+1=3n2+2n+4
其它算法复杂度表示法
- 大O表示法
表示了所有上限中最小的那个,上限。 - 大Ω表示法
表示了所有下限中最大的那个下限
f(n) = Ω(g(n))当且仅当g(n) = o(f(n)) - 大θ表示法
如果上下限相同,那么就可以用大θ表示 f(n) =θ(g(n)) 当且仅当f(n) = O(g(n))且f(n) = Ω(g(n))
3.变位词的判断问题
- 所谓“变位词”是指两个词之间存在组成字母的重新排列关系。如:heart和earth,python和typhon
- 可以很好展示同一问题的不同数量级算法
写一个bool函数,以两个词作 为参数,返回这两个词是否变位词
解法1:逐字检查
- 思路:将词1中的字符逐个到词2中检查是否存在,存在的字符“打勾”标记,如果每个字符都能找到,则两个词是变位词 。
- 将词2对应“打勾”的字符设为None。由于字符串是不可变类型,需要先复制到列表中:
def anagramSolution(s1,s2):
alist = list(s2) #复制s2到列表
stillOK = True
pos1 = 0
while pos1<len(s1) and stillOK:#循环s1每个字符
pos2 = 0
found = False
while pos2<len(alist) and not found:
if s1[pos1]==alist[pos2]:#在s2逐个对比
found=True
else:
pos2 = pos2 + 1
if found:
alist[pos2] = None#找到,打勾
else:
stillOK = False#未找到,失败
pos1 = pos1 + 1
return stillOK
print(anagramSolution("abcd","dcba"))
- 问题规模:词中包含的字符个数n
- 主要部分在于两重循环:
外层循环遍历s1每个字符,将内层循环执行n次
而内层循环在s2中查找字符,每个字符的对比次数,分别是1、2…n中的一个,而且各不相同 - 所以总执行次数是1+2+3+…+n,可知数量级O(n2)
解法2:排序比较
- 思路:将两个字符串都按照字母顺序排好序,再逐个字符对比是否相同
def anagramSolution2(s1,s2):
alist1 = list(s1)
alist2 = list(s2)
alist1.sort()
alist2.sort()
pos = 0
matches = True
while pos<len(s1) and matches:
if alist1[pos]==alist2[pos]:
pos = pos + 1
else
matches = False
return matches
- 粗看上去,本算法只有一个循环,最多执 行n次,数量级是O(n)
但循环前面的两个sort并不是无代价的,如果查询下后面的章节,会发现排序算法采用不同的解决方案,其运行时间数量级差不多是 O ( n2 ) 或者O(n log n),大过循环的O(n). - 所以本算法时间主导的步骤是排序步骤
- 本算法的运行时间数量级就等于排序过程的数量级O(n log n)
解法3:暴力法
- 暴力法解题思路为:穷尽所有可能组合
- 将s1中出现的字符进行全排列,再查看s2是否出现在全排列列表中
- n个字符全排列,是n!个字符,例如,对于20个字符长的词来说,将产生 20!=2,432,902,008,176,640,000个候选词,如果每微秒处理1个候选词的话,需要近8万年时间来做完所有的匹配。所以我们就不用这个算法了,它不是个好算法。
解法4.计数比较
- 若两个字符中每个字母出现次数相同,则为变位词
- 具体做法:为每个词设置一个26位的计数 器,先检查每个词,在计数器中设定好每 个字母出现的次数,计数完成后,进入比较阶段,看两个字符 串的计数器是否相同,如果相同则输出是 变位词的结论
def anagramSolution4(s1,s2):
c1 = [0]*26 #长度为26 全0的列表 代表每个字母出现的次数
c2 = [0]*26
for i in range(len(s1):
pos = ord(s1[i])-ord('a') #ord返回字符的unicode编码 字母的编码是连续的
c1[[pos] = c1[pos] + 1
for i in range(len(s2):
pos = ord(s2[i])-ord('a') #ord返回字符的unicode编码 字母的编码是连续的
c2[[pos] = c2[pos] + 1
j = 0
stillOK = True
while j<26 and stillOK:#计数器比较
if(c1[j]==c2[j]):
j = j + 1
else:
stillOK = False
return stillOK
- 前两个循环用于对字符串进行计数,操作次数等 于字符串长度n;第3个循环用于计数器比较,操作次数总是26次.
所以总操作次数T(n)=2n+26,其数量级为O(n)
这是一个线性数量级的算法,是4个变位词判断算法中性能最优的 - 本算法依赖于两个长度为26的计数器列表,来保存字符计数,这相比前3个算法需要更多的存储空间.(牺牲空间换时间)
4. Python数据类型的性能
- list和dict的常见操作
- list基本操作的大O数量级
- dict数据类型大O数量级
- 字典的执行时间与规模无关,是常数O(1)
而列表的执行时间则随着列表的规模加大,呈线性上升 O(n)