文章目录
一、时间复杂度
1、概念
一个算法的时间复杂度根据算法执行的基本运算数量来衡量
实例解析
import time
start_time=time.time()
for a in range(0,1001):
for b in range(0,1001):
for c in range(0,1001):
if a+b+c==1000 and a**2+b**2==c**2:
print(a,b,c)
end_time=time.time()
print('用时:%d'%(end_time-start_time))
运行结果
204
设时间复杂度为T,则根据执行的步骤得
T=1000 * 1000 * 10002
如果将题目改为a+b+c=2000
T=2000 * 2000 * 20002
则设a+b+c=n
T(n)=n3 * 2
如果对程序进行改进:
即c=1000-a-b,减少一次c从0到1000的遍历
import time
start_time=time.time()
for a in range(0,1001):
for b in range(0,1001):
c = 1000-a-b
if a**2+b**2==c**2:
print(a,b,c)
end_time=time.time()
print('用时:%d'%(end_time-start_time))
结果:
1
可知,此时时间复杂度为:
T=1000 * 1000 *2=n2 *2
2、大O表示法
根据实例解析,
- 当没有进行时间缩减时,T(n)=n3 2,
此时得时间复杂度是一个3次幂的函数,不管它的常数系数是多少,函数的走向不会变,都是n3,因此把它的时间复杂度称为g(n)=O(n3);* - 同理如果算法的基本运算数量为 *n2 2, 此时时间复杂度为 g(n)=O(n2);
- 如果算法的基本运算数量为为2,此时因为是常数,因此此时的时间复杂度为g(n)=O(1)
3、最坏时间复杂度
- 算法完成工作最少需要多少基本操作,即最优时间复杂度
- 算法完成工作最多需要多少基本操作,即最坏时间复杂度
- 算法完成工作平均需要多少基本操作,即平均时间复杂度
主要关注最坏时间复杂度,因为它提供了人一种保证,表明算法在此种程度的基本操作中一定能完成工作
4、时间复杂度的基本计算规则
- 基本操作,即只有常数,认为其时间复杂度为O(1)
- 顺序结构,时间复杂度按加法进行计算
- 循环结构,时间复杂度按乘法进行计算
- 分支结构,时间复杂度取最大值
- 判断一个算法的效率时,往往只需要关注操作数量的最高次项,其他次要项和常数项可以忽略
- 比如3n2 +2*n+1,先忽略常数,得到n2+n,再忽略次要项,得到时间复杂度为O(n2)
- 在没有特殊说明时,我们所分析的算法的时间复杂度指最坏时间复杂度
5、最常见的时间复杂度
执行次数函数举例 | 阶 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n2+2n+1 | O(n2) | 平方阶 |
5log2n+20 | O(logn) | 对数阶 |
2n+3nlog2n+19 | O(nlogn) | nlogn阶 |
6n3+2n2+3n+4 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
注意:经常将log2n(以2为底的对数)简写为logn
6、常见时间复杂度之间的关系
所消耗的时间从小到大:
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)<O(2n)<O(n!)<O(nn)
二、python内置类型性能分析
通过时间复杂度分析python内置函数类型的性能
1、timeit模块
timeit模块类似于time.time(),用来测试一小段python代码的执行速度
class timeit.
Timer实例化一个需要测试的实例:
Timer(stmt=‘pass’,setup=‘pass’,timer= < timer function > )
- Timer是测量小段代码执行速度的类
- stmt参数是要测试的代码语句(statment,类似于time.time())
- setup参数是运行代码时需要的设置(类似于import time)
- timer参数是一个定时器函数,与平台有关
具体测试语句为:
timer.timeit(number=1000000),timer为Timer实例
- Timer类中测试语句执行速度的对象方法
- number参数是测试代码时的测试次数,默认为1000000次
- 方法返回执行代码的平均耗时,一个float类型的秒数
2、创建list实例不同列表创建方法的时间
"""
用timeit模块中的Timer测试构造list的不同方法的性能
1、通过列表相加: li1=[1,2],li2=[23,5] li=li1+li2
2、通过列表生成器:li=[i for i in range(10000)]
3、通过将一个可迭代对象直接转换为列表:li=list(range(10000))
python2中range(100)返回的是列表,如果想要返回可迭代的对象使用xrange(10000)
python3中range(100)返回的是可迭代的对象
4、将值一个一个填充到空列表中:
li=[]
for i in range(10000):
li.append()
"""
from timeit import Timer
def test1():
li=[]
for i in range(10000):
li.append(i)
def test2():
li=[]
for i in range(10000):
li = li + [i]
def test3():
li=[i for i in range(10000)]
def test4():
li=list(range(10000))
def test5():
# 空列表后直接追加一个完整列表
li=[]
for i in range(1000):
li.extend([i])
def test6():
# 设置为在列表头部插入
li =[]
for i in range(1000):
li.insert(0,i)
# 需要三个参数
# 第一个为测试的函数,必须是字符串,因为Timer是在其他模块进行测试,需要的参数是代码本身
# 第二个参数是将要测试的内容导入测试模块时需要的设置,因为是当前模块所以__main__
# 第三个参数为timer定时器,根据系统不同而定,一般为默认
timer1=Timer('test1()','from __main__ import test1')
print('append列表尾部追加:',timer1.timeit(1000))
timer2=Timer('test2()','from __main__ import test2')
print('+:',timer2.timeit(1000))
timer3=Timer('test3()','from __main__ import test3')
print('列表生成器:',timer3.timeit(1000))
timer4=Timer('test4()','from __main__ import test4')
print('迭代器直接转换为列表:',timer4.timeit(1000))
timer5=Timer('test5()','from __main__ import test5')
print('extend:',timer5.timeit(1000))
timer6=Timer('test6()','from __main__ import test6')
print('insert列表头部插入:',timer6.timeit(1000))
测试结果:
aappend列表尾部追加: 0.6550495
+: 114.2445353
列表生成器: 0.3527528999999987
迭代器直接转换为列表: 0.2148532000000074
extend: 0.17351130000000126
insert列表头部插入: 0.42504220000000714
从结果可得,最快的是extend方法,最慢的是两个列表相加方法
而相对于append在列表尾部追加和insert在列表头部插入,append尾部追加更快,由python独特的数据结构决定
3、python内置操作的时间复杂度
基本类型只能包括:字符,整型,浮点型等
相对于list,dict相当于一个容器,封装了一些操作,所以不是基本类型
1)list内置操作
Operation | 大O表示法 | 操作描述 |
---|---|---|
index[ ] | O(1) | 按照索引取值 |
index assignment | O(1) | 按照索引赋值 |
append | O(1) | 尾部追加元素 |
pop() | O(1) | 尾部弹出元素 |
pop(i) | O(n) | 指定位置的元素弹出 |
insert(i,item) | O(n) | 指定位置插入元素 |
del operator | O(n) | 删除,一个一个删除 |
iteration | O(n) | 迭代 |
contains(in) | O(n) | 使用in操作符判断某个元素是否在列表中 |
get slice[x:y] | O(k) | 取x到y之间的切片,k为x~y的距离 |
del slice | O(n) | 删除切片,删除某段切片后,需将此切片后的所有元素向前填充 |
set slice | O(n+k) | 设置切片位置的值,首先需要将原来切片位置的值删除,再填充所以n+k |
reverse | O(n) | 反转 |
concatenate | O(k) | 将某个列表加到另一个列表中,k为某个列表的长度 |
sort | O(nlogn) | 排序 |
multiply | O(nk) | 列表乘常数,k为常数,n为列表长度 |
2)dict内置操作
Operation | 大O表示法 | 操作描述 |
---|---|---|
copy | O(n) | 复制 |
get item | O(1) | 取值 |
set item | O(1) | 设置某个键 |
delete item | O(1) | 删除某个键 |
contains(in) | O(1) | 查找某个键 |
iteration | O(n) | 迭代 |