数据结构和算法基础

一、尝试

先来看一道题:

如果 a+b+c = 1000, 且 a^2 + b^2= c^2 (a,b,c为自然数),如何求出 a、b、c可能的合并?

一般采取最原始的方法(枚举法),将 a、b、c分别从0~1000取值,再逐一匹配。

import time


start_time = time.time()
for a in range(1001):
    for b in range(10001):
        for c in range(1001):
            if a+b+c == 1000 and a**2+b**2 == c**2:
                print('a, b, c:%d, %d, %d', a, b, c)
end_time = time.time()

print("times:%d", end_time-start_time)

输出结果:

a, b, c:0, 500, 500
a, b, c:200, 375, 425
a, b, c:375, 200, 425
a, b, c:500, 0, 500
times:1102

可以看到花费了 1102 s ,消耗的时间是非常长的,并且在这期间非常的消耗cpu(ps:笔记本估计风扇都会转。)但是他其实是一个很简单的计算,完全没有必要去耗费这些来计算他。因此,引入了算法。

二、算法的提出

1、算法的概念

算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。一般地,当算法在处理信息时,会从输入设备或数据的存储地址读取数据,把结果写入输出设备或某个存储地址供以后调用。

算法是独立存在的一种解决问题的方法和思想。

对于算法而言,实现的语言不重要,重要的是思想。
本系列以python为例进行说明。

2、算法的五大特性

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

三、改进

我们稍微改进下上面的例子:

start_time = time.time()
for a in range(1001):
    for b in range(10001):
        # 省去了对于c的循环遍历以及 a+b+c=1000的判断
        c = 1000 - a -b
        if a**2+b**2 == c**2:
            print('a, b, c:%d, %d, %d'% (a, b, c))
end_time = time.time()

print("times:%d"% (end_time-start_time))

运行结果:

a, b, c:0, 500, 500
a, b, c:200, 375, 425
a, b, c:375, 200, 425
a, b, c:500, 0, 500
times:7

可以看到消耗时间已经极大地有了缩短。

四、算法效率衡量

1、执行时间反应算法效率

对于同一个问题,我们给出了两种解决算法,在两种算法的实现中,我对于程序的执行的时间进行了测算,发现两段程序执行的时间相差悬殊(1102秒相比于7秒),由此我们可以得出结论:实现算法程序的执行时间可以反应出算法的效率,即算法的优劣。

2、单靠时间值绝对可信吗?

假设我们将第二种尝试的算法程序运行在一天配置古老、性能低下的计算机中,情况会如何?很可能运行的时间并不会比我们的电脑中运行算法一的 1102 秒快多少。

单纯依靠运行的时间来比较算法的优劣并不一定是客观准确的。

程序的运行离不开计算机环境(包括硬件和操作系统),这些客观原因会影响程序运行的速度病反应程序的执行时间上。那么如何才能客观评判一个算法的优劣呢?

3、时间复杂度与“大O记法”

我们假定计算机执行算法每一个基本操作的时间是固定的一个时间单位,那么有多少个基本操作就代表会话费多少时间单位。算法对于不同的机器环境而言,确切的单位时间是不同的,但对于算法进行多少个基本操作(即话费多少单位时间)在规模数量级上确实相同的,由此可以忽略机器环境的影响而客观的反应算法的时间效率。

对于算法的时间效率,我们采用 “大O记法”来表示。

“大O记法”:对于单调的整数函数f,如果存在一个整数g和实常熟c>0,使得对于充分大的n总有f(n) <= c*g(n) ,就说函数g是f的一个渐进函数(忽略常熟),记为 f(n) = O(g(n))。也就是说,在趋向无穷的极限意义下,函数 f 的增长速度收到函数 g 的约束,即函数 f 与函数 g 的特征相似。

时间复杂度:假设存在函数 g ,使得算法 A 处理规模为 n 的问题,示例所用时间为 T(n) = O(g(n)) ,则称 O(g(n)) 为算法 A 的渐进时间复杂度,简称时间复杂度,记为 T(n)

4、如何理解“大O记法”

对于算法进行特别具体的细致分析虽然很好,但在时间中的实际价值有限。对于算法的时间性质和空间性质,最重要的是其数量级和趋势,这些是分析算法效率的主要部分。而计量算法基本操作数量的规模函数中那些常量因子可以忽略不记。例如,可以认为 3n2 和 100n2 属于同一量级,如果两个算法处理同样规模实例的代价分别为这两个函数,就认为他们的效率“差不多”, 都是 n2级。

5、最坏时间复杂度

分析算法时,存在几种可能的考虑:

  1. 算法完成工作最少需要多少基本操作,即 最优时间复杂度
  2. 算法完成工作最多需要多少基本操作,即 最坏时间复杂度
  3. 算法完成工作平均需要多少基本操作,即 平均时间复杂度

6、时间复杂度的几条基本计算规则

  1. 基本操作:即只有常数项,认为其时间复杂度为 O(1)
  2. 顺序结构:时间复杂度按加法进行计算
  3. 循环结构:时间复杂度按乘法进行计算
  4. 分支结构:时间复杂度取最大值
  5. 判断一个算法的效率时,旺旺只需要关注操作数量的最高次项,其他次要项和常数项可以忽略
  6. 在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度

五、算法分析

那么我们来分析一下前面所用到的两种算法的时间复杂度
1、第一次:

for a in range(1001):
    for b in range(10001):
        for c in range(1001):
            if a+b+c == 1000 and a**2+b**2 == c**2:
                print('a, b, c:%d, %d, %d'% (a, b, c))

时间复杂度: T(n) = O(nnn) = O(n3)

2、第二次:

for a in range(1001):
    for b in range(10001):
        c = 1000 - a -b
        if a**2+b**2 == c**2:
            print('a, b, c:%d, %d, %d'% (a, b, c))

时间复杂度: T(n) = O(nn(1+1)) = O(n*n) = O(n2)

六、常见时间复杂度

在这里插入图片描述
注意:经常将log2n(以2为底的对数)简写为 log n

常见时间复杂度之间的关系
在这里插入图片描述
所消耗的时间从小到大:
在这里插入图片描述

七、Python内置类型性能分析

1、timeit模块:

timeit模块可以用来测试一小段python代码的执行速度

class timeit.Timer(stmt = 'pass', setup = 'pass', timer = <timer function>)
  • Timer 是测量小段代码执行速度的类
  • stmt 参数是要测试的代码语句
  • setup 参数是运行代码时需要的设置
  • timer 参数是一个定时器函数
timeit.Timer.timeit(number=1000000)

Timer类中测试语句执行速度的对象方法。number参数是测试代码时的测试次数,默认为 1000000 次。方法返回执行代码的平均耗时,一个 float 类型的秒数。
实例:
我们这里计算python的列表的各种操作的次数来演示这个函数的用法。

from timeit import Timer


def test1():
    li = []
    for i in range(10000):
        li.append(i)

def test2():
    li = []
    for i in range(10000):
        li += [i]

def test3():
    li = [i for i in range(10000)]

def test4():
    li = list(range(10000))

def test5():
    li = []
    for i in range(10000):
        li.extend([i])


t1 = Timer('test1()', 'from __main__ import test1')
print('append' ,t1.timeit(number=1000), "seconds")
t2 = Timer('test2()', 'from __main__ import test2')
print('+' ,t2.timeit(number=1000), "seconds")
t3 = Timer('test3()', 'from __main__ import test3')
print('[i for i in range(1000)]' ,t3.timeit(number=1000), "seconds")
t4 = Timer('test4()', 'from __main__ import test4')
print('list' ,t4.timeit(number=1000), "seconds")
t5 = Timer('test5()', 'from __main__ import test5')
print('extend' ,t5.timeit(number=1000), "seconds")

输出结果:

append 0.6194634630010114 seconds
+ 0.6978286960002151 seconds
[i for i in range(1000)] 0.3362328119983431 seconds
list 0.2075852699999814 seconds
extend 0.8540526190045057 seconds

我们可以清楚地看出每个程序运行1000次所消耗的时间

2、list内置操作的时间复杂度

在这里插入图片描述

3、dict内置操作的时间复杂度

在这里插入图片描述

八、数据结构

1、概念

数据是一个抽象的概念,将其进行分类后得到程序设计语言中的基本类型,如:int、float、char等。数据元素之间不是独立的,存在特定的关系,这些关系便是结构。数据结构指数据对象那个中数据元素之间的关系。

python给我们提供了很多现成的数据结构类型,这些系统自己定义好的,不需要我们自己去定义的数据结构叫做 python的内置数据结构,比如列表、元祖、字典。而有些数据组织方式,python 系统里面没有直接定义,需要我们自己去定义实现这些数据的组织方式,这些数据组织方式成为 python的扩展数据结构,比如栈、队列等。

2、算法与数据结构的区别

数据结构只是静态的描述了数据元素之间的关系。
高效的程序需要在数据结构的基础上设计和选择算法。
程序 = 数据结构 + 算法

总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体

3、抽象数据类型(Abstract Data Type)

数据抽象类型(ADT)的含义是指一个数据模型以及定义在此数学模型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。引入抽象数据类型的目的是把数据类型的表示和数据类型上的运算的实现与这些数据类型和运算在程序中的引用隔开,使他们相互独立。
五种最常用的数据运算:

  • 插入
  • 删除
  • 修改
  • 查找
  • 排序
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值