contents
算法的概念
算法法是独立存在的一种解决问题的方法和思想。计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。
算法的五大特性
- 输入: 算法具有0个或多个输入
- 输出: 算法至少有1个或多个输出
- 有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
- 确定性:算法中的每一步都有确定的含义,不会出现二义性
- 可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成。
算法效率衡量
单纯依靠运行的时间来比较算法的优劣并不一定是客观准确的。程序运行的计算机环境(包括硬件和操作系统),这些客观原因会影响程序运行的速度并反应在程序的执行时间上。
时间复杂度=基本运算数量总和
假定计算机执行算法每一个基本操作的时间是固定的一个时间单位,那么有多少个基本操作就代表会花费多少时间单位。对于不同的机器环境而言,确切的单位时间是不同的,但是对于算法进行的基本操作个数(即花费的时间单位)在规模数量级上却是相同的,由此可以忽略机器环境的影响而客观的反应算法的时间效率。
“大O记法”
对算法进行特别具体的细致分析在实践中的实际价值有限。而对于算法的时间性质和空间性质,最重要的是其数量级和趋势,这些是分析算法效率的主要部分。而计量算法基本操作数量的规模函数中那些常量因子可以忽略不计。例如,可以认为
3
n
2
3n^2
3n2和
100
n
2
100n^2
100n2属于同一个量级,为
n
2
n^2
n2级。
g
g
g是
f
f
f的一个渐近函数:对于单调的整数函数
f
f
f,如果存在一个整数函数
g
g
g和实常数
c
>
0
c>0
c>0,使得对于充分大的n总有
lim
x
→
∞
f
(
n
)
g
(
n
)
=
c
≠
0
,
{\lim_{x \to \infty}} \frac{f(n)}{g(n)}=c\not=0,
x→∞limg(n)f(n)=c=0,则记为
f
(
n
)
=
O
(
g
(
n
)
)
f(n)=O(g(n))
f(n)=O(g(n))。
note:
- 算法的速度指的并非时间,而是操作数的增速。
- 谈论算法的速度时,大O表示法说的是随着输入的增加,其运行时间将以什么样的速度增加。
- 算法的运行时间用大O表示法表示。
时间复杂度
时间复杂度:假设存在函数 g g g,使得算法A处理规模为n的问题示例所用时间为 T ( n ) = O ( g ( n ) ) T(n)=O(g(n)) T(n)=O(g(n)),则称 O ( g ( n ) ) O(g(n)) O(g(n))为算法A的渐近时间复杂度,简称时间复杂度,记为 T ( n ) T(n) T(n)。
复杂度 | 说明 | 意义 |
---|---|---|
最坏时间复杂度 | 算法完成工作最多需要的基本操作 | 提供了一种保证,表明算法在此种程度的基本操作中一定能完成工作。 |
最优时间复杂度 | 算法完成工作最少需要的基本操作 | 反映的只是最乐观最理想的情况,没有参考价值。 |
平均时间复杂度 | 算法完成工作平均需要的基本操作 | 对算法的一个全面评价,但这种衡量并没有保证而且,对于平均情况的计算,因为应用算法的实例分布可能并不均匀而难以计算。 |
主要关注算法的最坏情况,亦即最坏时间复杂度。
时间复杂度的基本计算规则
程序 | 时间复杂度计算 | 示例 |
---|---|---|
基本操作 | 即只有常数项,认为其时间复杂度为O(1) | i**2+j**2==(1000-i-j)**2包含乘积减法和判断的基本操作 |
顺序结构 | 按加法进行 | 基本操作的累加,如下面两个print操作 |
循环结构 | 按乘法进行计算 | for循环了1000次要乘 |
分支结构 | 取最大值 | if条件满足时要进行2个基本操作,否则无,故取2 |
下面算法的时间复杂度为 T ( n ) = O ( n ∗ n ∗ ( 1 + m a x ( 1 , 0 ) ) ) = O ( n ∗ n ) = O ( n 2 ) T(n) = O(n*n*(1+max(1,0))) = O(n*n) = O(n2) T(n)=O(n∗n∗(1+max(1,0)))=O(n∗n)=O(n2)
for i in range(1001):
for j in range(1001-i):
if i**2+j**2==(1000-i-j)**2:
print('a={},b={},c={}'.format(i,j,1000-i-j))
print('{}**2+{}**2={}**2'.format(i,j,1000-i-j))
判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略。
所消耗的时间从小到大
O
(
1
)
<
O
(
l
o
g
n
)
<
O
(
n
)
<
O
(
n
l
o
g
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
Python内置类型性能分析
timeit模块可以用来测试一小段Python代码的执行速度。
class timeit.Timer(stmt=‘pass’, setup=‘pass’, timer=<timer function>)
Timer是测量小段代码执行速度的类。
stmt参数是要测试的代码语句(statment);
setup参数是运行代码时需要的设置;
timer参数是一个定时器函数,与平台有关。
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
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 test2_p():
li=[]
for i in range(10000):
li+=[i]
def test3():
li=[]
for i in range(10000):
li.insert(i,i)
def test4():
li=[ i for i in range(10000)]
def test5():
li=list(range(10000))
def test6():
li=[]
for i in range(10000):
li.extend([i])
timer1=Timer(stmt='test1()', #第一个参数要传的是代码,而这里的test1()只是一个字符串,所以需要配合第二个参数的设置,
setup='from __main__ import test1') #因为并不是在本文件中测试,所以要在别的文件中导入test01()函数,本文件一旦启动,就是__main__
timer2=Timer(stmt='test2()',
setup='from __main__ import test2')
timer2_p=Timer(stmt='test2_p()',
setup='from __main__ import test2_p')
timer3=Timer(stmt='test3()',
setup='from __main__ import test3')
timer4=Timer(stmt='test4()',
setup='from __main__ import test4')
timer5=Timer(stmt='test5()',
setup='from __main__ import test5')
timer6=Timer(stmt='test6()',
setup='from __main__ import test6')
print('append所花费的时间为',timer1.timeit(1000))
print('li+[i]所花费的时间为',timer2.timeit(1000))
print('li+=[i](不同于+,存在优化)所花费的时间为',timer2_p.timeit(1000))
print('insert(i,i)所花费的时间为',timer3.timeit(1000))
print('[ i for i in range(10000)]所花费的时间为',timer4.timeit(1000))
print('list(range(10000)) 所花费的时间为',timer5.timeit(1000))
print('extend 所花费的时间为',timer6.timeit(1000))
# append所花费的时间为 0.6048508999956539
# li+[i]所花费的时间为 132.05659050000395
# li+=[i](不同于+,存在优化)所花费的时间为 0.8107264999998733
# insert(i,i)所花费的时间为 1.3892121000026236
# [ i for i in range(10000)]所花费的时间为 0.3711252000066452
# list(range(10000)) 所花费的时间为 0.16723130000173114
# extend 所花费的时间为 1.2171440000020084
list和dict内置操作的时间复杂度
list类型和dict类型是不能算作基本的数据类型的,计算机所使用的基本数据类型只包括字符、整型、浮点型等。而list和dict相当于一个容器。计算的基本操作也是不包括list内置操作和dict内置操作的。
list的内置操作的时间复杂度
dict的内置操作的时间复杂度
数据结构
为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越快越好,于是就需要考虑数据究竟如何保存的问题,这就是数据结构。
数据
数据是一个抽象的概念,将其进行分类后得到程序设计语言中的基本类型,如:int,float,char等。数据元素之间不是独立的,存在特定的关系,这些关系便是结构。
数据结构指数据对象中数据元素之间的关系,是对基本数据类型的一次封装,如列表,元组,字典等。数据结构静态地描述了数据元素之间的关系。
程序 = 数据结构 + 算法
算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
抽象数据类型(Abstract Data Type)
抽象数据类型的含义是指一个数学模型以及定义在此数学模型上的一组操作(类似抽象代数),即把数据类型和数据类型上的运算捆在一起,进行封装。
引入抽象数据类型的目的:把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。
最常用的数据运算有五种:
插入、删除、修改、查找、排序