拜师教育第一阶段day1_python数据结构与算法

第一章     数据结构与算法的基本概念

1.没有数据结构与算法组织的程序存在以下三个缺点:(1)面对问题没有解决思路,无从下手。(2)没有考虑程序的效率与空间的开销,性能低下。(3)根据别人的程序暂时解决问题,但相对其进行优化时,不知道如何针对性的下手

2.算法的本质:一种解决问题的思路。算法是计算机处理信息的本质。当算法在处理信息时,会从输入设备或数据的存储地址读取数据,把结果写入输出设备或某个存储地址供以后再调用。

3.算法的四大特征:(1)算法至少可以有0个输入,但至少有一个输出;(2)算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成(有穷性);(3)算法中的每一步都有确定的含义,不会出现二义性(确定性);(4)算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成(可行性)。

4.针对“如果 a+b+c=1000(可以替换为n:问题的规模),且 a^2+b^2=c^2(a,b,c 为自然数),如何求出所有a、b、c可能的组合?”

(I)初始想法:这样的问题,我们可以考虑采用“枚举abc组合”的方法一个一个去试。在不断的组合试错中找到符合要求的解。这是一种解决这类问题的思想,它实现的计算机语言无关。因此,可以考虑采用均在0~1000范围内遍历a,b,c的各种组合情况,但要把握好三个for遍历循环的内外层嵌套关系。

(2)优化算法:考虑到输出上述代码运行时间144s(与机型相关),效率太低,于是利用问题的第一个条件(知二求一)可以在a,b确定后定下具体的c值,三个数之间是有一层相对关系的,因此可以用一个基本数据操作代替一层循环,即c=1000-a-b,并因此取消了第一个判断条件。经过两种代码的对比发现效率与算法有关

# -*- coding:utf-8 -*-
import time
start_time=time.time()
for a in range(1001):   #第一种思想代码
    for b in range(1001):
        for c in range(1001):
            if a+b+c==1000 and a**2+b**2==c**2:
                print('a={1},b={2},c={3}'.format(a,b,c))
'''
for a in range(1001):   #第二种思想代码
    for b in range(1001):
        c=1000-a-b
        if a**2+b**2==c**2:
            print('a={1},b={2},c={3}'.format(a,b,c))
'''
print('程序结束!')
end_time=time.time()
print('运行耗时:%d'%(end_time-start_time))
#补充:上句中通过%d将输出值保留整数,中间用%将字符串与数值计算结果连接

        另外,当问题规模发生变化时,其解决思路并未发生变化,所以学习算法的核心在于学习其思想。

5.有上述实践可知,实现算法程序的执行时间可以反应出算法的效率,即算法的优劣。但单纯依靠运行的时间来比较算法的优劣并不一定是客观准确的!因为这个运行时间跟计算机的机型与配置情况有关。虽然每台机器运行的总时间不同,但执行基本操作的步骤数量是一致的。因此将程序执行基本操作的步骤的总数记为时间复杂度,用来描述两个算法的优劣

        对于以上两种算法,(1)T(n)=n*n*n*(8+1+1)=10n^3,其中外面三层循环为嵌套结构采用乘法,另外条件判断句中共8步(第一步:a+b+c第二步:a+b+c==1000第三步:a^2第四步:b^2第五步:c^2第六步:a^2+b^2第七步:a^2+b^2==c^2第八步:a+b+c==1000 and a^2+b^2==c^2),''.format()运算一步,print()输出一步。(2)T(n)=n*n*(1+5+1+1)=8n^2。

       对于算法的时间复杂度进行特别具体的细致分析虽然很好,但在实践中的实际价值有限。对于算法的时间性质和空间性质,最重要的是其数量级和趋势。一般而言,常系数不影响时间复杂度随问题规模的变化,只会影响其陡峭性。所以常将常系数(视作1)与常数项(视作0)忽略,这时得到的函数就叫做T(n)的渐进函数,即其大O表示法。至此,在分析时间复杂度时,可以将与问题规模n无关的步骤看做常数项,也就是基本操作,上面的if a+b+c==1000 and a**2+b**2==c**2与print('a={1},b={2},c={3}'.format(a,b,c)),以及 c=1000-a-b,if a**2+b**2==c**2与print('a={1},b={2},c={3}'.format(a,b,c))均为基本操作,每步时间复杂度可看做O(1)。(将T(n)去常数项与常系数,找到里边相比其他项耗时最多的那项即得渐进函数g(n)。)

6.时间复杂度的分类:(1)算法完成工作最少需要多少基本操作,即最优时间复杂度,这是情况最特殊最理想的一种情况,不做深入研究,其实际意义不大;(2)算法完成工作最多需要多少基本操作,即最坏时间复杂度,它提供了一种保证,表明算法在此种程度的基本操作中一定能完成工作。(3)算法完成工作平均需要多少基本操作,即平均时间复杂度,但这种衡量并没有保证,不是每个计算都能在这个基本操作内完成。因此对于以上三种,主要关注(最坏)时间复杂度

7.时间复杂度的基本计算规则:(1)基本操作,即只有常数项,认为其时间复杂度为O(1);(2)顺序结构(基本步骤累加,比连续执行2次print()),时间复杂度按加法进行计算;(3)循环结构,时间复杂度按乘法进行计算;(4)分支结构,时间复杂度取最大值(考虑多个分支中最坏的那个分支,符合最坏时间复杂度的定义);(5)判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略;(6)在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度。因此前面两种算法的时间复杂度为O(n^3)与O(n^2)。

8.常见时间复杂度之间的关系:所消耗的时间从小到大排列为:

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^2logn)<O(n^3)<O(2^n)<O(n!)<O(n^n)

9.对于python中的列表,其尾部追加函数append(),任意位置插入函数insert(),虽然调用时只有一句,但不属于基本操作步骤,其封装于函数体中的代码才能体现其具体的时间复杂度。因此在python中,常常要调用内置类型性能分析模块timeit测试一段代码的执行速度。

具体实现方法:导入timeit模块中的计时器类Timer(from timeit import Timer),生成该类实例对象timer1=Timer(第一个参数为预测试的代码,第二个参数"from __main__ import x(在该模块下独立执行时main,假设x为代表测试代码段的变量名)",第三个参数与操作平台有关(一般缺省)),以对象方法timer1.timeit(number=1000)测试执行代码1000次耗时的平均值。

10.list内置操作的时间复杂度:

时间复杂度的解释:与索引相关的两个操作是根据给定的列表起始地址加索引偏移量之间进行一次运算便可访问,所以是O(1);append()与pop()是直接对末尾元素进行操作,一步便可操作完成,O(1);但pop(i)当i=0时要弹出第一个元素,这时后边所有的元素都要前移所以O(n);insert()考虑最坏情况,当i等于0时,这时列表所有元素都要后移,为插入到偏移地址为0的新元素腾空间,为O(n);del 元素则其后每个都要复制前移,故O(n);iteraction(迭代器)需要对整个列表遍历访问O(n);要看列表中是否含有某元素需要遍历每个元素,O(n);get slice[x:x+k]首先根据x偏移量一步找到list[x],之后依次访问并取出k个元素,所以O(k);del slice如果只删除第一个元素,则后续每个元素都要前移为O(n);set slice一步定位到插入切片的位置,但如果考虑最坏情况为在表头部插,则O(n),之后再将k个元素依次插入O(k),总的即为O(n+k);reverse逆序排列需要遍历整个列表所以为O(n);concatenate(连接)操作首先直接定位到队尾,再依次将待连接的k个元素添加到相对尾部所以为O(k);multiply需要将长度为n的列表复制k遍,所以时间复杂度为O(nk)。

append与extend的区别:append接收单个元素添加到列表尾部,不生成新列表对象;extend接收列表对象添加到原列表尾部。

dict内置操作的时间复杂度:

时间复杂度的解释:copy字典要对每个键值对进行操作,所以O(n);get item直接根据键可以访问该键值对一步完成,则为O(1);set item直接放入键一步完成或者最坏替换原有键值对两步完成属于基本操作,所以O(1);del item一步直接删除该键值对O(1);contains判断某个键值对是否属于字典,只需要比较字典中有无该键,一步操作O(1);iteration迭代器需要对整个字典进行遍历,所以时间复杂度应为字典的长度,即键值对的个数为O(n)。

11.数据结构:为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越快越好,于是我们就需要考虑数据究竟如何保存的问题,这就是数据结构。例如以下两种存储结构:列表和字典都可以存储一个班的学生信息,但是想要在列表中获取一名同学的信息时,就要遍历这个列表,其时间复杂度为O(n),而使用字典存储时,可将学生姓名作为字典的键,学生信息作为值,进而查询时不需要遍历便可快速获取到学生信息,其时间复杂度为O(1)。数据结构指数据对象中数据元素之间的关系。实际上,字典、列表、元组、集合已经是对基本数据类型(整型、浮点型、字符串)的组合,已经相当于python 中的几种数据结构。

数据结构与算法的联系:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体,简而言之,程序=数据结构+算法

12.抽象数据类型(ADT相当于python中的API,是一种面向对象的设计思想):把数据类型和数据类型上的运算捆在一起,进行封装。一般再开始的时候只需要写明建立什么类,包含什么函数,类与函数的功能分别是什么暂时不用考虑代码的实现(函数体中可以添加pass占位)。最常见的数据操作包括:插入、删除、修改、查找、排序

13.(补)作业:利用内置类型性能分析函数测试为一个空列表添加1-1000数字的各种方法的平均耗时。(要求使用列表‘+’,append,insert,列表生成器,range创建法,extend。)

# -*- coding:utf-8 -*-
from timeit import Timer
def test1():
    li1=[]
    for x in range(1000):
        li1=li1+[x]    #每次都生成新表,耗时耗空间
def test2():
    li2 = []
    for x in range(1000):
        li2.append(x)   # 不生成新表,尾部追加模式
def test3():
    li3 = []
    for x in range(1000):
        li3.insert(x,x)   # 不生成新表,尾部插入模式
def test4():
    li4=[x for x in range(1000)]   # 列表生成器
def test5():
    li5 = list(range(1000))
def test6():
    li6= []
    for x in range(1000):
        li6.extend([x])   #不生成新表,在尾部添加新表
def test7():
    li7=list()
    for i in range(1000):
        li7.insert(0,i)   #不生成新列表,在头部插入新元素
def test8(a):
    for i in range(1000): #从尾部弹出元素
        a.pop()
def test9(b):
    for i in range(1000): #从头部弹出元素
        b.pop(0)

t1=Timer('test1()',"from __main__ import test1")
print('+:',t1.timeit(number=1000))
t2=Timer('test2()',"from __main__ import test2")
print('append:',t2.timeit(number=1000))
t3=Timer('test3()',"from __main__ import test3")
print('insert:',t3.timeit(number=1000))
t4=Timer('test4()',"from __main__ import test4")
print('列表生成器:',t4.timeit(number=1000))
t5=Timer('test5()',"from __main__ import test5")
print('range:',t5.timeit(number=1000))
t6=Timer('test6()',"from __main__ import test6")
print('extend:',t6.timeit(number=1000))
t7=Timer('test7()','from __main__ import test7')
print('从头部插入元素需要时间:%f'%t7.timeit(number=1000))
t8=Timer('test8([x for x in range(10000)])','from __main__ import test8')
print('从尾部弹出元素所需时间:%f'%(t8.timeit(number=1000)))
t9=Timer('test9([x for x in range(10000)])','from __main__ import test9')
print('从头部弹出元素所需时间:%f'%(t8.timeit(number=1000)))

写以上代码时,要注意:首先,pythonfile的名字中不要含有关键字test和其他标准库或者第三方库的名字(abc也不行也是库文件名),另外若在主程序中执行Timer()第二个参数为from   __main__   import···;另外,要注意li=li+[i]与li+=[i]的时间复杂度是不一致的,第一个是每次加i后生成新表,第二个是进行了计算优化的增强型赋值操作,表示对li列表原对象本身在末尾进行添加[i]的操作,这样相当于li.extend([i])。

注意:运行这个文件应该:创建项目----创建新的package----创建该.py文件,出错后将缓存的文件夹删去,不要使用关键字为函数命名,也不要在此文件中使用数字,这样基本可以保证正常运行。

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值