O 定义对函数f(n) 和g(n) 如果存在c>0 和 n0 >1,使得 f(n)<=c*g(n);就说 f(n) 是g(n)的 O或者说f(n) 是O(g(n))
使用O来描述程序运行时间:算法的执行时间是执行次数的函数
用来做算法分析的七个函数;其增长率如图所示;计算效率除了与算法本身有关,还与计算机的计算能力有关
前缀平均值实例的算法效率分析:
def prefix_average1(S):
n=len(S)
A=[0]*n
for j in range(n):
total=0
for i in range(j+1):
total+=S[i]
A[j]=total/j+1
return A
def prefix_average2(S):
n=len(S)
A=[0]*n
for j in range(n):
A[j]=sum(S[0:(+1)])/j+1
return A
def prefix_average3(S):
n=len(S)
A=[0]*n
for j in range(n):
total+=S[j]#利用了前面计算的和,不用重新计算和,因此计算效率更高
A[j]=total/j+1
return A
前两个算法的都是O(n^2),第三个算法的计算效率是O(n)
三集不相交问题:
假设给出三个序列,任意一个序列没有重复元素,但是不同序列之间肯有重复元素,判断三个序列有没有交集
def disjoint(A,B,C):
for a in A:
for b in B:
for c in C:
if a==b==c:
return False
return True
def disjiont2(A,B,C):
for a in A:
for b in B:
if a==b:
for c in C:
if c ==a:
return False
return True
#第一个算法是O(n^3)第二个的算法是O(n^2)
元素唯一性:求序列S中是否所有元素都不同:
def unique1(S):
for j in range(len(S)):
for k in range(j+1,len(S)):
if S[j]==S[K]:
return False
return True
def unique2(S):
temp=sorted(S)
for j in range(len(S)):
if temp[j-1]==temp[j]:
return False
return True
#第一个算法为 O(n^2), 第二个算法的为n*log(n)
注:sorted 函数的循环运行时间是 n*log(n)
def find(S,val):#在序列S中寻找第一次出现的val,并返回其索引
n=len(S)
j=0
while j<n:
if S[j]==val:
return j
j+=1
return -1
用时间复杂度来描述程序的效率,但是不能直接用时间来表示,因为执行效率还与计算机硬件有关
时间复杂度有最优时间复杂度和最坏时间复杂度以及平均时间复杂度
一般只关注最坏时间复杂度;
时间复杂度的几条基本计算规则:
1.基本操作,即只有常数项,认为其时间复杂度为O(1)
2.顺序结构,时间复杂度按加法进行计算
3.循环结构,时间复杂度按乘法进行计算
4.分支结构:时间复杂度取最大值
5.判断一个算法的效率时,往往只需要关注操作数量的最高次项,其他次要项和常数项可以忽略
6.在没有特殊说明时,我们所分析的算法的时间复杂度指的是 最坏时间复杂度
时间复杂度的表示方式:用T(n)表示
时间复杂度从小到大排列:
o(1)<O(log(n))<n < nlogn <n^2< n^3 <a^n <n! <n^n
调用 函数的时候其时间复杂度需要看这个函数内部的代码
timeit模块:用来测试一小段代码的执行速度
timeit.Timer(stmt='pass',setup='pass',timer=<timer function>)
第一个参数是需要测试的代码,第二个参数是运行 代码时需要的设置,timer是定时器
测试的时候用:timeit.Timer.timeit(number)参数是测试代码的测试次数,默认为1000000,返回平均时间的浮点数格式
import timeit
from timeit import Timer
li1=[1,2]
li2=[3,4]
li=li1+li2 #直接通过加操作生成新列表,注意是维度相加,并不是元素相加得到[1, 2, 3, 4]
li=[i for i in range(1000)] #通过列表生成器生成列表
li=list(range(10000)) #直接将可迭代对象通过list转换成列表
def test1():
li=[]
for i in range(10000):#通过循环生成列表
li.append(i)
def test2():
li=[]
for i in range(10000):
li+=[i]
def test3():
li=[i for i in range(1000)] #通过列表生成器生成列表
def test4():
li=list(range(10000)) #直接将可迭代对象通过list转换成列表
timer1=Timer('test1()',"from __main__ import test1")
#第一个参数是需要测试的代码,封装在函数中然后以把函数名以字符串
#作为参数,第二个参数是需要测试时候的操作设置,测试会自动在另一个文件执行,此时的文件名即代码所在的文件名变为 __main__
print("1:",timer1.timeit(1000))#会返回测试所用的平均时间,单位是s
timer2=timeit.Timer('test2()',"from __main__ import test2")
a=timer2.timeit(1000)
print("2:",a)
timer3=timeit.Timer('test3()',"from __main__ import test3")
print("3:",timer3.timeit(1000))
timer4=timeit.Timer('test4()',"from __main__ import test4")
print("4:",timer4.timeit(1000))
def test5():
li=[]
for i in range(10000):
li.append(i)#尾部添加
def test6():
li=[]
for i in range(10000):
li.insert(0,i) #尾部添加
timer5=timeit.Timer('test5()',"from __main__ import test5")
print("5:",timer5.timeit(1000))
timer6=timeit.Timer('test6()',"from __main__ import test6")
print("6:",timer6.timeit(1000))
output:
1: 0.7137577860000874
2: 1.3980658909999875
2: 1.0125639150001007
3: 0.03197327099996983
4: 0.14158055600000807
5: 0.5788076310000179
6: 26.77082987299991
#5和6说明尾部添加比头部添加更高效