Python数据结构与算法——Day1
算法的概念
算法是计算机处理信息的本质,计算机程序的本质是由一个算法来告诉计算机确切的步骤来执行一个指定的任务。一般地,当算法在处理信息时,会从输入设备或者数据的存储地址读取数据,把结果写入输出设备或某个地址以供后续使用
对于算法而言,实现的语言(Python,C,C++)并不重要,重要的是思想
算法的特性
- 输入:算法具有0个或者多个输入
- 输出:算法至少具有1个或多个输出
- 有穷性:算法会在有限个步骤之后结束,并且每一个步骤在可接受的时间内完成
- 确定性:算法的每一步都由确切的含义
- 可行性:算法的每一步都是可行的
算法的效率
通过执行时间反应算法效率
对于同一问题的两种算法,前者执行的时间短于后者,由此我们得出结论:程序的执行时间长短可以反应出算法的效率,即算法的优劣
单靠时间的长短来判断可信吗
假设我们将前者所对应的程序在一台老旧的计算机上执行,那么可能会发现执行时间居然比后者还要长!
所以,单纯依靠运行时间来比较算法的优劣不一定是客观准确的
因为程序的执行离不开计算机环境(包括硬件和操作系统),这些客观因素会影响程序运行的速度并反应在执行时间上
时间复杂度与“大O记法”
那么什么样的方法才是客观的呢?
我们假设计算机执行算法的每一个基本操作的时间是一个固定的时间单位,那么有多少个基本操作就会花费多少个时间单位。对于不同的机器而言,时间单位的确切数值是不知道的,但是对于算法需要几个基本操作(花费多少个时间单位),即对于基本操作的规模确是相同的,由此则可忽略计算机环境而客观反应算法的时间效率
大O记法
对于算法的时间效率,用“大O记法”表示,其定义如下:
对于单调的整数函数f,如果存在一个整数函数g和实数常数c>0,使得对于无穷大的n总有f(n)<=c*g(n),那么就认为g是f的一个渐进函数,记为f(n)=O(g(n)),即在极限下,函数f的增长速度受到函数g的约束,f与g的特征相似
时间复杂度
假设存在函数g,使得算法A处理规模为n的问题时所用的时间单位为Tn=O(g(n)),则称O(g(n))为A的渐进时间复杂度,简称时间复杂度,记为T(n)
简化“大O记法”
分析算法的基本操作步骤规模时,细致的分析虽能获得精确结果,但在实践中往往关注其数量级和趋势,而对于规模计算种的那些常量则忽略不计
最坏时间复杂度
分析算法时,存在几种可能的考虑:
- 算法完成最少需要几个基本操作,即最优时间复杂度
- 算法完成最多需要几个基本操作,即最坏时间复杂度
- 算法完成平均需要几个基本操作,即平均时间复杂度
我们主要关注的是算法的最坏情况,即最坏时间复杂度
计算时间复杂度的基本规则
- 基本操作,即只有常数项时,认为其时间复杂度为O(1)
- 顺序结构,时间复杂度累加
- 循环结构,时间复杂度累积
- 分支结构(如if选择语句),时间复杂度取最大值
- 判断一个算法的时间效率时,只关注其操作数量的最高次项,其它项及项前的常数系数可忽略
- 在无特殊说明时,时间复杂度就是最坏时间复杂度
操作示例:
for a in range(1001):
for b in range(1001):
for c in range(1001):
if a**2+b**2==c**2 and a+b+c==1000:
print((a,b,c))
上述例子中的时间复杂度为:
T(n)=O(nnn*1)=O(n^3)
for a in range(1001):
for b in range(1001-a):
c=1000-a-b
if a**2+b**2=c**2:
print((a,b,c))
上述例子中的时间复杂度为:
T(n)=O(nn(1+1))=O(n*n)=O(n^2)
由此可见,第二种算法优于第一种
常见时间复杂度
阶 | 描述 |
---|---|
O(1) | 常数阶 |
O(n) | 线性阶 |
O(n^2) | 平方阶 |
O(logn) | 对数阶 |
O(nlogn) | nlog阶 |
O(n^3) | 立方阶 |
O(2^n) | 指数阶 |
所花费的时间从小到大:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2^n) <
O(n!) < O(n^n)
Python内置性能分析
timeit模块
该模块可用来测试一小段程序的执行速度
timeit.Timer(stmt='pass',setup='pass',timer=<timer function>)
Timer是测试小段代码执行速度的类
stmt是要测试的语句
setup是运行代码时需要的设置
timer时一个定时器函数,与计算平台有关
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
操作示例:
def test1():
l=[]
for i in range(1000):
l=l+[i] # 注意该表达与l+=[i]不同,后者在运算速度上做过优化
def test2():
l=[]
for i in range(1000):
l.append(i)
def test3():
l = [i for i in range(1000)]
def test4():
l = list(range(1000))
from timeit import Timer
t1=Timer("test1()","from __main__ import test1")
print("+",t1.timeit(number=1000),"seconds")
t2=Timer("test2()","from __main__ import test2")
print("append",t2.timeit(number=1000),"seconds")
t3=Timer("test3()","from __main__ import test3")
print("comprehension",t3.timeit(number=1000),"seconds")
t4 = Timer("test4()", "from __main__ import test4")
print("list range ",t4.timeit(number=1000), "seconds")
'''
('+', 1.7890608310699463, 'seconds')
('append', 0.13796091079711914, 'seconds')
('comprehension', 0.05671119689941406, 'seconds')
('list range', 0.014147043228149414, 'seconds')
'''
pop 操作测试
x = range(2000000)
pop_zero = Timer("x.pop(0)","from __main__ import x")
print("pop_zero ",pop_zero.timeit(number=1000), "seconds")
x = range(2000000)
pop_end = Timer("x.pop()","from __main__ import x")
print("pop_end ",pop_end.timeit(number=1000), "seconds")
'''
('pop_zero ', 1.9101738929748535, 'seconds')
('pop_end ', 0.00023603439331054688, 'seconds')
'''