1、算法引入
数据结构与算法 :
我们把整个写代码的过程比喻为打仗! 代码 是兵卒还有武器,那么我们便是将军,数据结构与算法就是一个我们所谓的兵法还有计谋!
例子:如果a+b+c = 1000 , 且a^2 + b^2 =c^2(勾股定理,abc为自然数),如何求出所有a、b、c的组合?
1、枚举法 (一个一个的去试一试,最复杂的方法):
#coding=utf8
import time
#需求:如果a+b+c = 1000 , 且a^2 + b^2 =c^2(勾股定理,abc为自然数),如何求出所有a、b、c的组合?
#枚举法
# a
# b
# c
# 思路:三者依次变化,c 从0-1000 ,然后 b 从 1-1000 一个一个试一试
#代码实现
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:{}'.format(a,b,c))
print('a b c : %d , %d , %d' %(a,b,c))
end_time = time.time()
print("finish: %d"%(end_time-start_time))
这就是一个算法:算法的概念:说白了算法就是你告诉计算机每一步应该去怎么做。就是把解决问题的思路用程序实现,交给计算机去运行!跟语言没有任何关系。
1、算法的五大特征:
1、输入:算法具有0个或者是多个输入
2、输出:算法至少有一个或者是多个输出
3、有穷性:算法在有限个步骤以后会自动结束而不是无限的循环,并且每一个步骤可以在接受的时间范围内结束运行
4、确定性:算法中的每一步都都自己的含义,不会出现二义性
5、可行性:给出的思路最后能用计算机语言实现出来。
2、算法的时间复杂度与大O表示法的由来已经表示理解:
上面的例子,现在我们想要将效率提升怎么办!?
思路:当我们给出来a,b的值以后呢! 那么c 的值就确定了! 就是c = 1000 - a - b! 那么代码就可以改进了:
#coding=utf8
import time
#需求:如果a+b+c = 1000 , 且a^2 + b^2 =c^2(勾股定理,abc为自然数),如何求出所有a、b、c的组合?
#枚举法
# a
# b
# c
# 思路:三者依次变化,c 从0-1000 ,然后 b 从 1-1000 一个一个试一试
#代码实现
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:{}'.format(a,b,c))
# print('a b c : %d , %d , %d' %(a,b,c))
#第二次改进
#c = 1000 -a -b
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 : %d , %d , %d' % (a, b, c))
# print('a:{},b:{},c:{}'.format(a,b,c))
end_time = time.time()
print("finish: %d"%(end_time-start_time))
E:\python\python.exe E:/pythonstudy/pycharm/DataStructureStudy/studyone.py
a b c : 0 , 500 , 500
a b c : 200 , 375 , 425
a b c : 375 , 200 , 425
a b c : 500 , 0 , 500
finish: 1
时间上大幅度的提高:那么问题来了!我们怎么样衡量一个算法的好坏呢?通过算法的效率来衡量。
1、执行时间上能够反应算法的效率 : 就像上一个例子!通过时间,一个一百多秒,一个1秒! 很直观的反应出来了算法的效率问题。就是算法的优劣。
但是只在时间就评论算法的优劣!可靠吗!??不可靠。还得跟环境来相比。 由于这样!我们提出来了时间复杂度的说法。
这样想: 虽然每台机器的执行总时间不一样! 但是无论在哪个机器上,执行基本运算数量相同 ,就是每一步应该怎么运算。所以我们可以通过这个算法到底运行了多少步来衡量这个算法的优劣问题。
我们这样来理解时间复杂度: 我们把执行 基本步骤 运算数量 的总和,叫做时间复杂度。看清楚!是基本步骤的执行数量,也就是执行这个算法,总共执行了多少步! 称为时间复杂度。
时间复杂度的数学解释:看上面的例子:第一个写法执行了T=1000*1000*1000 *10(细分的话是10,不细分的话就是每一步是一个步骤)
那么如果是算2000 、3000呢!N呢? 如果是n的话:就是 T(n)=n^3 *2 : 这个T(n) :就称为第一个写法的时间复杂化度,看清楚了,这个T(n)计算的是执行了多少步,并不是时间!
我们评估的时候不需要这么细致!G(n) = n^3 。我们可以理解这个G(n)和T(n)是属于同一个级别的算法。为什么呢?在数学中,这两个函数的大体走势并不是相差太多!属于渐进函数的关系, 也就是说G(n) 是T(n)的一个渐进函数!这个G(n)的时间复杂度可以和T(n)这样的一个时间复杂度平级。
大O表示法:大白话!就是提出来一个跟n相关的计算步骤,然后把跟n相关的系数全部忽略掉,只留下最特征的。就是我们所谓的大O表示法。就像那个例子:G(n) 就是上面程序的大O表示法。
3、最坏时间复杂度与计算规则:
3、1:算法的时间复杂度分的几种状况:
1、算法完成工作最少需要多少基本操作:就是最优时间复杂度。
2、算法完成工作最多需要多少基本操作:就是最坏时间复杂度。
3、算法完成工作平均需要多少基本操作:就是平均时间复杂度。
我们只关注最坏时间复杂度,对于最优时间复杂度太理想了!没什么价值,对于平均是时间复杂度,不是每一个计算都能在这个基本操作内完成,而且我们应用算法实例的时候分布不均匀,可能有的时间复杂度大! 有的小,所以我们只关注算法的最坏情况,也就是最坏是将复杂度。
3、2 :那么我们的时间复杂度应该怎么计算呢:看一下时间复杂度的几条基本计算规则
1、基本操作:也就是当只有常数项的时候,我们认为他的时间复杂度为O(1)、
2、顺序结构:就比如for循环下面两个print() 就是一个顺序结构!这时候时间复杂度需要使用加法进行计算
3、循环结构:就比如两个for循环,我们需要使用乘法来进行时间复杂度的计算。
4、分支结构:对于时间复杂度计算的话!需要取两个分支里面的最大值
5、判断一个算法的效率的时候,往往只需要关注操作数量的最高此项,其他次要项和常数可以忽略,就像是上面案例中的T(n) , 我们只需要关注最高次项,忽略掉系数还有常数!也就只需要关注G(n)
6、在没有特殊说明的时候,我们所分析的算法的时间复杂度都是指的是最坏时间复杂度。
3、常见的时间复杂度之前的大小关系:
他们之间的大小的关系是什么呢?
上面就是直接体现了时间复杂度的比较!哪个大哪个小!
4、代码执行时间测量模块 :
timeit模块:用来测试一小段Python代码的执行速度。
语法: class timeit.Timer(stmt='pass' , setup='pass' , time=<timer function>)
语法解释:stmt 参数是要测试的代码语句(statment) ,就是需要计算时间复杂度的代码。
setup参数是运行代码时需要的设置。
timer参数是一个定时函数,与平台无关。通常不设置。
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均耗时,一个float类型的秒数。
例子使用:
>>> import timeit
#执行命令
>>> t2 = timeit.Timer('x=range(1000)')
#显示时间
>>> t2.timeit()
10.620039563513103
#执行命令
>>> t1 = timeit.Timer('sum(x)', 'x = (i for i in range(1000))')
#显示时间
>>> t1.timeit()
0.1881566039438201
或者如下使用:
In [1]: from timeit import timeit as timeit
In [2]: timeit('x=1')
Out[2]: 0.03820111778328037
In [3]: timeit('x=map(lambda x:x*10,range(32))')
Out[3]: 8.05639690328919
对于函数的可以这样使用:
#coding=utf8
def test1():
n=0
for i in range(101):
n+=i
return n
def test2():
return sum(range(101))
def test3():
return sum(x for x in range(101))
if __name__=='__main__':
from timeit import Timer
t1=Timer("test1()","from __main__ import test1")
t2=Timer("test2()","from __main__ import test2")
t3=Timer("test3()","from __main__ import test3")
print(t1.timeit(10000))
print(t2.timeit(10000))
print(t3.timeit(10000))
print(t1.repeat(3,10000))
print(t2.repeat(3,10000))
print(t3.repeat(3,10000))
输出结果如下:
E:\python\python.exe E:/pythonstudy/pycharm/DataStructureStudy/timertest.py
0.04966825833620816
0.01299798628397554
0.05478934021444143
[0.08756273261483324, 0.04969852607073022, 0.0702528705117143]
[0.022848857520656796, 0.021958694388507893, 0.018193461148263035]
[0.055861109753722715, 0.0516425898386475, 0.052332912988650726]
Process finished with exit code 0
5、列表类型不同操作的时间效率:
#coding=utf8
from timeit import Timer
def test1():
li = []
for i in range(100000):
li.append(i)
def test2():
li = []
for i in range(100000):
li += [i]
def test3():
li = [i for i in range(100000)]
def test4():
li = list(range(100000))
def test5():
li = []
for i in range(100000):
li.extend([i])
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('tuidao:: ' , timer3.timeit(1000))
timer4 = Timer('test4()','from __main__ import test4')
print('list::',timer4.timeit(1000))
timer5 = Timer('test5()','from __main__ import test5')
print('extend:',timer5.timeit(1000))
输出时间:
E:\python\python.exe E:/pythonstudy/pycharm/DataStructureStudy/02_list.py
append: 11.108818578449034
+: 16.257810186610108
tuidao:: 6.248306097174954
list:: 4.219168415878123
extend:: 19.248454518800315
Process finished with exit code 0
可见使用list的形式是最快的!同时使用insert往列表的头部添加!要比append 往列表的尾部追加要慢的很多,为什么呢!?这是根据数据的存储方式来决定的。、
6、python中列表与字典操作的时间复杂度:
list内置操作的时间复杂度:
dict内置操作的时间复杂度:
7、数据结构的引入:对数据的组织方式不同!那么我们使用的算法也不相同,这就是数据结构的概念,就是一组数据我们应该怎么样去存储。 程序 = 数据结构+算法。 算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。、