引言
计算机科学
首先明确的一点就是计算机科学不仅仅是对计算机的研究,虽然计算机在科学发展的过程中发挥了重大的作用,但是它只是一个工具,一个没有灵魂的工具而已。所谓的计算机科学实际上是对问题、解决问题以及解决问题的过程中产生产生的解决方案的研究。例如给定一个问题,计算机科学家的目标是开发一个算法来处理该问题,最终得到该问题的解、或者最优解。所以说计算机科学也可以被认为是对算法的研究。
因此我们也可以感受到,所谓的算法就是对问题进行处理且求解的一种实现思路或者思想。
算法
案例引导
一个常胜将军在作战之前都会进行战略的制定,目的是为了能够在最短的时间切成本消耗最低的情况下获取最终的胜利。如果将编码作为战场,则程序员就是这场战役的指挥官,你如何可以将你的程序可以在最短且消耗资源最小的情况下获取最终的执行结果呢?算法就是我们的策略!
意义
- 数据结构和算法思想的通用性异常的强大,在任何语言中都被使用,它们将会是我们编码生涯中伴随我们最长久利器(左膀右臂)。有一定经验的程序员最终拼的就是算法和数据结构。
- 数据结构和算法思想也可以帮助我们拓展和历练编码的思维,可以让我们更好的融入到编程世界的角角落落。
算法分析
- 案例引入:刚接触编程的学生经常会将自己编写的程序和别人的程序做比对,获取在比对的过程中会发现双方编写的程序很相似但又各不相同。那么就会出现一个有趣的现象:两组程序都是用来解决同一个问题的,但是两组程序看起来又各不相同,那么哪一组程序更好呢?例如下述代码:
问题
a+b+c = 1000 a**2 + b**2 = c**2 (a,b,c均为自然数),求出a,b,c可能的组合?
方法1:
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)
方法2:
for a in range(0,1001):
for b in range(0, 1001):
c = 1000 - a - b
if a+b+c == 1000 and a**2+b**2 == c**2:
print(a, b, c)
分析:
必须采用某种量化的方式求出不同算法的资源耗费和执行效率的具体值来判定算法之间的优劣.
方法1: 消耗计算机资源和执行效率(无法直观)
方法2: 计算算法执行的耗时(不推荐, 会受机器和执行环境的影响)
方法1: -> 221.7778379917145
方法2: -> 1.410386085510254
方法3: 计算算法的时间复杂度 (推荐)
- 时间复杂度:量化算法需要的操作或者执行步骤的数量。
时间复杂度
- 评判规则: 量化算法执行的操作/执行步骤的数量
- 最重要的项: 时间复杂度表达式中最有意义的项
1. 描述
时间复杂度是同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。
2. 定义
在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
3. 算法复杂度
算法复杂度分为时间复杂度和空间复杂度。其作用:时间复杂度是指执行算法所需要的计算工作量;
空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)。
4. 计算方法
1) 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得T(n)/f(n)的极限值(当n趋近于无穷大时)为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
分析:随着模块n的增大,算法执行的时间的增长率和 f(n) 的增长率成正比,所以 f(n) 越小,算法的时间复杂度越低,算法的效率越高。
2) 在计算时间复杂度的时候,先找出算法的基本操作,然后根据相应的各语句确定它的执行次数,再找出 T(n) 的同数量级(它的同数量级有以下:1,log2n,n,n log2n ,n的平方,n的三次方,2的n次方,n!),找出后,f(n) = 该数量级,若 T(n)/f(n) 求极限可得到一常数c,则时间复杂度T(n) = O(f(n))
例如:
for a in range(0,1001):
for b in reange(0, 1001):
c = 1000 - a - b # 该步骤属于基本操作执行次数:n的平方次
if a+b+c == 1000 and a**2+b**2 == c**2: # 该步骤属于基本操作执行次数:n的三次方次
print(a, b, c)
则有T(n) = n**3 + n**2,根据上面括号里的同数量级,我们可以确定 n的三次方 为T(n)的同数量级
则有f(n) = n**3,然后根据 T(n)/f(n) 求极限可得到常数c
则该算法的时间复杂度:T(n) = O(n^3) 注:n^3即是n的3次方。
3) 在pascal中比较容易理解,容易计算的方法是:看看有几重for循环,只有一重则时间复杂度为O(n),二重则为O(n^2),依此类推,如果有二分则为O(logn),二分例如快速幂、二分查找,如果一个for循环套一个二分,那么时间复杂度则为O(nlogn)。
案例分析
- 常见时间复杂度:O(1)< O(logn)< O(n)< O(nlogn)< O(n^2)< O(n^3)< O(2^n) < O(n!)< O(n^n)
例题:
a=5
b=6
c=10 # 3 个赋值语句, 操作步骤 3n
for i in range(n):
for j in range(n):
x = i * i
y = j * j
z = i * j # 两层循环 + 三个语句 = 3n^2 (操作步骤)
for k in range(n):
w = a*k + 45
v = b*b # 操作步骤: 2n
d = 33 # 操作步骤: 1
分析:
1) 经过分析: T(n) = 3n^2+2n+4
2) 通过查看指数,我们可以看到 n^2 项是最重要的,因此这个代码段是 O(n^ 2)。当 n 增大时,所有其他项以及主项上的系数都可以忽略。
数据结构
示例
- 对于数据(基本类型的数据(int,float,char))的组织方式就被称作为数据结构。数据结构解决的就是一组数据如何进行保存,保存形式是怎样的。
- 案例:需要存储一些学生的学生信息(name,score)
1) 那么这些数据应该如何组织呢?
- 组织形式1:
[
{'name':'zhangsan','score':100},
{'name':'lisi','score':99}
]
- 组织形式2:
[
('zhangsan',100),
('lisi',99)
]
- 组织形式3:
{
'zhangsan':{'score':100},
'lisi':{'score':99}
}
2) 查询某一个具体学生的时间复杂度是什么呢?
- 三种组织形式基于查询的时间复杂度分别为:O(n),O(n),O(1)
- 发现:python中的字典,列表,元组本身就是已经被封装好的一种数据结构啦。使用不同的数据结构进行数据的存储,所导致的时间复杂度是不一样。因此认为算法是为了解决实际问题而设计的,数据结构是算法需要处理问题的载体。
python数据结构的性能分析
1. 列表
- python 的设计者在实现列表数据结构的时候有很多选择。每一个这种选择都可能影响列表操作的性能。为了帮助他们做出正确的选择,他们查看了最常使用列表数据结构的方式,并且优化了实现,以便使得最常见的操作非常快。
- 在列表的操作有一个非常常见的编程任务就是是增加一个列表。我们马上想到的有两种方法可以创建更长的列表,可以使用 append 方法或拼接运算符。但是这两种方法那种效率更高呢。这对你来说很重要,因为它可以帮助你通过选择合适的工具来提高你自己的程序的效率。
- 实例化一个空列表,然后将0-n范围的数据添加到列表中。(四种方式)
方式1:
def test01():
alist = []
for i in range(1000):
alist = alist + [i]
方式2:
def test02():
alist = []
for i in range(1000):
alist.append(i)
方式3:
def test03():
alist = [i for i in range(1000)]
方式4:
def test04():
alist = list(range(1000))
timeit 模块
timeit模块:该模块可以用来测试一段python代码的执行速度/时长。
Timer类:该类是timeit模块中专门用于测量python代码的执行速度/时长的。
原型为:class timeit.Timer(stmt='pass',setup='pass')。
- stmt参数:表示即将进行测试的代码块语句。
- setup:运行代码块语句时所需要的设置。
- timeit函数:Timer.timeit(number=100000),该函数返回代码块语句执行number次的平均耗时。
from timeit import Timer
def test01():
alist = []
for i in range(1000):
alist = alist + [i]
def test02():
alist = []
for i in range(1000):
alist.append(i)
def test03():
alist = [i for i in range(1000)]
def test04():
alist = list(range(1000))
if __name__ == '__main__':
t1 = Timer('test01()','from __main__ import test01')
print(t1.timeit(number=1000)) # 1.427245729042511
t2 = Timer('test02()','from __main__ import test02')
print(t2.timeit(number=1000)) # 0.08398291617004361
t3 = Timer('test03()','from __main__ import test03')
print(t3.timeit(number=1000)) # 0.03463117467885013
t4 = Timer('test04()','from __main__ import test04')
print(t4.timeit(number=1000)) # 0.015171500463509346