算法的引入
如果a+b+c=1000,且a2+b2 = c2,求a,b,c的所有可能组合**
最简单粗暴的方式
import time
strat_time = time.time()
for a in range(1000):
for b in range(1000):
for c in range(1000):
if a+b+c == 1000 and a**2 + b**2 == c**2:
print(f'a{a},b:{b},c{c}')
end_time = time.time()
print(f'end:{end_time - strat_time}s')
# 时间很久,预估我的电脑的十几分钟才能算完
算法的概念
算法是计算机处理信息的本质,计算机本质上是一个算法告诉计算机确切的步骤来执行一个指定的任务。当算法在处理信息时,通过输入设备或数据存储的地址获取数据,执行完毕后,再返回给输出设备或某个存储地址供以后调用。
算法是独立存在的一种解决问题的思想和方法
实现算法的语言不限(c, c++, java, python)
算法的五大特性
输入:0或多个输入
输出:至少一个或多个输出
又穷:算法执行的步骤应该是有限的,不是无限循环,且每个步骤执行的时间都应该在可接受范围内
确定:算法没有不都有明确的目标,不出现二义性
可行:算法每一步应该是可行的,且有限次的
代码优化
我的思路,当a和b确定了,c就是一个定值,没有必要再做循环,c = 1000-a-b
我们假设b为0,a = 500,c=500则满足条件,这是一个临界值,a超过500的值一定不满足条件,若a=501,c=499,平方后一定不满足,往后的情况也是如此
所以循环500次足矣
import time
strat_time = time.time()
for a in range(501):
for b in range(501):
c = 1000 - a - b
if a+b+c == 1000 and a**2 + b**2 == c**2:
print(f'a{a},b:{b},c{c}')
end_time = time.time()
print(f'end:{end_time - strat_time}s')
# a0,b:500,c500
# a200,b:375,c425
# a375,b:200,c425
# a500,b:0,c500
# end:0.416762113571167s
算法效率衡量
从上面两端代码可以看出,效率明显提高,但单靠时间可以反映算法的效率吗?
显然不可以,我的电脑是5代i7,如果这段代码在5代i3上运行,其运行时间会增加,这跟cpu的核心数和线程数有关,单核存在物理极限
我的算法肯定不是最优解,如果我的循环体量变大,实现100000甚至更多,时间也会变长
单看时间的问题:
- 测试结果非常依赖测试环境
- 测试结果受数据规模的影响很大
大O复杂度表示法
公式:T(n) = O(f(n))
其中T(n)表示代码的执行时间,n表示数据规模的大小,f(n)表示执行的总次数,O表示T(n)和f(n)成正比
我们第一段代码运行了多少次?
三个循环100010001000,一个判断语句和一个打印语句,我们粗略当2次计算
f(1000) = 1000100010002,假设执行n次
f(n) = n**32,系数为2,我们用O表示这种呈现正比的关系
即T(n) = O(n**3)
这就是大O时间复杂度表示法,它并不是表示具体的代码执行时间,而是一种趋势,执行时间与数据规模增长变化的趋势,也叫渐进时间复杂度,简称时间复杂度。
时间复杂度分析
- 只关注循环次数最多的一段代码
- 加法法则,总复杂度等于量级最大的那段代码的复杂度
def fn(n):
sum = 0
for i in range(n):
sum += i
print(i)
fn(5)
# 1
# 2
# 3
# 4
# 5
这段代码中 T(n) = O(n)
def fn(n):
sum = 0
for i in range(100):
sum += i
for i in range(n):
sum += i
for i in range(n):
for j in rang(n):
pass
这段代码中大O时间复杂度分别是,O(1),O(n),O(n**2)
最坏时间复杂度
算法完成最少需要多少基本操作,即最优时间复杂度
- 反映的只是最乐观理想的情况,没有参考价值
算法完成最多需要多少基本操作,即最坏时间复杂度 - 提供了一种保证,该算法至少能完成
算法完成平均需要多少基本操作,即平均时间复杂度 - 意义不大,可能因为算法实例分布不均而难以计算
我们要关注的是最坏时间复杂度
常见的时间复杂度
执行次数函数 | 阶 | 非正式术语 |
---|---|---|
12 | O(1) | 常熟阶 |
2n+3 | O(n) | 线性阶 |
3n^2+3n+1 | O(n^2) | 平方阶 |
log2n+20 | O(log2n) | 对数阶 |
2n+3nlog2n+19 | O(nlogn) | nlogn阶 |
2^n | O(2^n) | 指数阶 |
python内置类型性能分析
timeit模块
timeit可以用来测试一小段代码python代码的执行速度
class timeit.Timer(stmt='pass', setup='pass', timer=<timer.function>)
# Timer是测量小段代码执行速度的类
# stmt参数要测试的代码语句(statment)
# setup参数是运行代码时需要的设置
# timer参数是一个定时器函数,与平台有关
timeit.Timer.timeit(number = 1000000)
# Timer是timeit类中测试语句执行速度的对象方法。number是测试代码时的测试次数,默认是1000000次。方法返回执行代码的平均耗时
# 返回的是float形式的秒数
测试
通过不同的列表创建方式,对比执行时间的长短
from timeit import Timer
def ad():
l = []
for i in range(100):
l += [i]
def app():
l = []
for i in range(100):
l.append(i)
def ld():
l = [i for i in range(100)] # 列表推导式
def ist():
l = []
for i in range(100):
l.insert(0, i)
def force():
list(range(100))
t1 = Timer('ad()', 'from __main__ import ad')
print('add:', t1.timeit(number=1000))
t2 = Timer('app()', 'from __main__ import app')
print('append:', t2.timeit(number=1000))
t3 = Timer('ld()', 'from __main__ import ld')
print('list derivation:', t3.timeit(number=1000))
t4 = Timer('ist()', 'from __main__ import ist')
print('insert:', t4.timeit(number=1000))
t5 = Timer('force()', 'from __main__ import force')
print('force:', t5.timeit(number=1000))
# add: 0.1202428
# append: 0.10098230000000002
# list derivation: 0.055870699999999995
# insert: 0.38531930000000003
# force: 0.02027749999999995
强制转换>列表推导式>append>add>insert
数据结构
数据是一个抽象的概念,将其进行分类后得到程序设计语言中的基本类型。如:int,float,char等。数据元素之 间不是独立的,存在特定的关系,这些关系便是结构。数据结构指数据对象中数据元素之间的关系。
我们如何使用python来保存一个学生的信息
student_d = {
'cral':{
'age':18,
'major':'english'
}
}
print(student_d['cral']['age'])
student_l = [
('cral', 'age', 18),
('cral', 'major', 'english')
]
print(student_l[0])
# 18
# ('cral', 'age', 18)
字典的时间复杂度O(1),列表的O(n)
我们为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储 方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越快越好,于是我们就需要考虑数据 究竟如何保存的问题,这就是数据结构。
python中提供了现成的数据结构类型,如列表、字典、元组,这种称为内置数据结构。
我们也可以自己去定义数据结构,如栈、队列,这种称为拓展数据结构。
算法与数据结构的区别
数据结构只是静态描述了数据元素之间的关系
高效的程序需要再数据结构的基础上设计和选择算法
程序 = 数据结构 + 算法
总结:算法是为了解决实际需求而设计的,数据结构是算法需要处理问题的载体
抽象的数据类型(Abstract Data Type)
抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上 的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据 类型和运算在程序中的引用隔开,使它们相互独立。
class Stu(object):
def add(self):pass
def pop(self):pass
def sort(self):pass
def modify(self):pass