数据结构和算法Python版
算法的提出
我们最终写好运行的程序比作战场,我们是将军,Python是士兵和兵器,数据结构和算法则是兵法。
引入:
第一次尝试
如果 a+b+c = 1000 且a的平方+b的平方等于c的平方(abc都是自然数),如何求出a、b、c可能的组合?
# 枚举法
# a
# b
# c
import time
a1 = 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:%d,%d,%d'%(a,b,c))
a2 = time.time()
print('经过了',a2 - a1,'时间')
print('完成')
输出结果:
a,b,c:0,500,500
a,b,c:200,375,425
a,b,c:375,200,425
a,b,c:500,0,500
经过了 104.03584575653076 时间
完成
算法的提出:
算法的概念: 算法是处计算机的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。一般的,当算法在处理信息时,会从输入设备或者数据的存储地址读取数据,把结果写入输出设备或者某个储存地址供以后使用。
算法是独立存在的一种解决问题的方法和思想。
对于算法而言,实现的语言并不重要,重要的是思想。
算法五大特性:
- 输入: 算法具有0个或者多个输入。
- 输出: 算法至少有一个或者多个输出
- 有穷性:算法在有限的步骤后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完后完成
- 确定性:算法的每一步都是有确定的含义,不会出现二义性
- 可行性:算法的每一步都是可行的,也就是说每一步都是能够执行有限的次数完成
第二次尝试尝试:
# 枚举法
# a
# b
# c
import time
a1 = 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:%d,%d,%d'%(a,b,c))
# T(复杂度) = 1000(外层循环的循环体) * 1000(中层循环的循环体) * 1000(最内层循环体的内容) * 2(if内的)
# 如果 a+b+c == 2000 可以把1000换成2000即可 所以:
# T = N * N * N * 2
# 时间复杂度:T(n) = n^3 * 2
# 假如说有:T(n) = n^3 * 10 是跟上面一个数量级(n^3)的
# 可以总结为:T(n) = N^3 * k
# g(n) = n^3 这里n^3就是它的大O表示法 称之为 T(n) = O(g(n))
# 则称为O(g(n))这个算法的渐进时间复杂度,简称为时间复杂度。即为T(n)
# 改进后:
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))
# 改良后的时间复杂度度T(n) = n * n * (1 + max(1,0)) = n^2 * 2 = n^2 * 2 = O(n^2)
# 每台机器执行的总时间不同
# 但是执行基本运算数量大体相同
# 时间复杂度与'大O记法'
a2 = time.time()
print('经过了',a2 - a1,'时间')
print('完成')
# [1,3,5,2,1,6,7,4] 排序算法: n^2
# [2,1,5,8,3,2,4,5]
# for i in lis:
# 时间复杂度的计算:
# 基本顺序: 即是只有常数项,则认为时间复杂度是O(1)
# 顺序: 时间复杂度是按照加法进行
# 循环: 时间复杂度按照乘法进行
# 条件: 时间复杂度取最大值
# 判断一个算法的效率时,往往只需要关注操作数量的最高选项,其他次要项和常数项可以忽略
# 在没有特殊情况说明时,我们分析的算法时间复杂度指的是最坏的时间复杂度
# 小技巧: 找跟N相关的
# li = []
# li.append()
# li.insert()
# 文件名和包名最好不要重合
# 存
# name
# age
# hometown
# 有
# 列表: [ ('张三'),24,'北京' ]
# for stu in stu: if stu(0) = '张三':
# [ {'name':'张三','age':23,'hometown':'北京'},]
# stus['张三']
# {'张三':{'age' = 12,'hometown':''},}
输出结果:
a,b,c:0,500,500
a,b,c:200,375,425
a,b,c:375,200,425
a,b,c:500,0,500
经过了 0.8205840587615967 时间
完成
补充:最坏时间复杂度:
分析算法时,存在几种可能的考虑:
算法完成工作最少需要多少基本操作,即最优时间复杂度
算法完成工作最多需要多少基本操作,即最坏时间复杂度
算法完成工作平均需要多少基本操作,即平均时间复杂度
对于最优时间复杂度,其价值不大,因为它没有提供什么有用信息,其反映的只是最乐观最理想的情况,没有参考价值。
常见的时间复杂度:
常见时间复杂度之间的关系:
Python内置类型性能分析
timeit模块
timeit模块可以用来测试一小段Python代码的执行速度。
class timeit.Timer(stmt='pass',setup='pass',time=<timer function>)
Timer
是测量小段代码执行速度的类
stmt
参数是要测试的代码语句(statment)
setup
参数是运行代码时需要的设置
timer
参数是一个定时器函数,与平台有关
timeit.Timer.timeit(number = 1000000)
Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为1000000次。方法返回执行代码的平均消耗时,一个float类型的秒数。
测试:
# 回顾四种方式生成列表
# li1 = [1,2]
# li2 = [3,4]
# li = li1 + li2
# li = [i for i in range(10000)]
# li = list(range(10000))
# 测试
from timeit import Timer
def t1():
li = []
for i in range(1000):
li.append(i)
def t2():
li = []
for i in range(1000):
li = li + [i] #这里会产生新的对象 所以特别耗时间
# li += [i] #这里不会产生新的对象 差不多相当于extend()方法了
def t3():
li = [i for i in range(1000)]
def t4():
li = list(range(1000))
def t5():
li = []
for i in range(1000):
li.extend([i])
# timer1 = Timer("t1()","from __main__ import t1") #只能传进去字符串 而不是直接传方法
# print('append:',timer1.timeit(1000)) # 测算一千次
#
# timer2 = Timer("t2()","from __main__ import t2") #只能传进去字符串 而不是直接传方法
# print('+:',timer2.timeit(1000)) # 测算一千次 每次都会产生新的对象 所以慢 所以要少用
#
# timer3 = Timer("t3()","from __main__ import t3") #只能传进去字符串 而不是直接传方法
# print('[i for i in range]:',timer3.timeit(1000)) # 测算一千次
#
# timer4 = Timer("t4()","from __main__ import t4") #只能传进去字符串 而不是直接传方法
# print('range(10000):',timer4.timeit(1000)) # 测算一千次
#
# timer5 = Timer("t5()","from __main__ import t5") #只能传进去字符串 而不是直接传方法
# print('extend:',timer5.timeit(1000)) # 测算一千次
def t6():
li = []
for i in range(1000):
li.append(i)
def t7():
li = []
for i in range(1000):
li.insert(0,1)
timer6 = Timer("t6()","from __main__ import t6") #只能传进去字符串 而不是直接传方法
print('append:',timer6.timeit(1000)) # 测算一千次 尾部添加比较快
timer7 = Timer("t7()","from __main__ import t7") #只能传进去字符串 而不是直接传方法
print('insert:',timer7.timeit(1000)) # 测算一千次 头部添加比较慢
print('Over')
输出:
+: 0.80130577
[i for i in range]: 0.024587888000000002
range(10000): 0.010526264000000007
extend: 0.08086561299999995
append: 0.05559057800000011
insert: 0.3460714330000001
Over
数据结构概念
我们可以将数据储存到列表 元祖 字典 集合之类的地方,但是我们要根据数据的储存方式实现进行处理,那么数据的储存方式不同就会导致需要不同的算法进行处理。我们希望算法解决问题的效率越来越好,于是我们需要考虑数据究竟如何保存,这就是数据结构。
比如说: 列表和字典就是Python内建帮我们封装好的两种数据结构。
概念
数据是一个抽象的概念,将其进行分类后得到的程序设计语言的基本类型,如 int float char等。数据元素之间不是独立的,存在特定的关系,这种关系就是结构。数据结构是指数据对象中数据元素之间的关系。
Python内置数据结构有: 列表、元祖、字典等。但是有些数据结构组织方式,Python系统里面并没有直接定义,需要我们自己去定义实现这些数据结构的组织方式,这些数据结构组织方式称之为Python的扩展数据结构。比如 栈、队列等。
算法和数据结构的区别
数据结构只是静态的描述了数据元素之间的关系。
高校的程序需要在数据结构的基础之上设计和选择算法。
程序 = 数据结构 + 算法
总结: 算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。
# 存
# name
# age
# hometown
# 有
# 列表: [ ('张三'),24,'北京' ]
# for stu in stu: if stu(0) = '张三':
# [ {'name':'张三','age':23,'hometown':'北京'},]
# stus['张三']
# {'张三':{'age' = 12,'hometown':''},}
抽象数据类型(Abstract Data Type)
抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上运算的实现与这些数据类型和运算在程序中的引用隔开,使它们相互独立。
最常见的数据类型有五种:
- 插入
- 删除
- 修改
- 查找
- 排序
class Stus(Object):
def adds(self):
def pow(self):
def sort(self):
def modify(self):