自然数: 非负整数
100==a 常量放左边,防止少写了一个=号,但是又不报错.
算法的概念
算法是独立存在的一种解决问题的方法和思想,对于算法而言,实现的语言不重要,重要的是思想.
算法的五大特性
1 输入:算法具有0个或多个输入
2 输出: 算法至少有1个或多个输出
3 有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
4 确定性: 算法中每一步都有确定的含义,不会出现二义性
5 可行性: 算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成
执行时间反应算法效率
实现算法程序的执行时间可以反应出算法的效率,即算法的优劣
算法复杂度与’大O记法’
对于不用的机器环境而言,确切的时间单位是不同的,但是对于算法进行多少个基本操作(即花费多少时间单位)在规模数量级上确是相同的,由此可以忽略机器环境的影响而客观的反应算法的时间效率.
对于算法的时间效率,我们可以用’大O记法’来表示
‘大O记法’ 对于单调的整数函数f, 如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)
基本运算总数 是衡量算法效率的一个标准
时间复杂度
时间的不可靠,是由于计算机环境的不同所致的.
程序运行的总的时间=基本运算总数 * 基本运算的单位时间(机器性能越好 花费的基本运算的单位时间越少)
T(n),n表示问题的规模,T表示基本运算总数, T(n)这个函数就表示的是时间复杂度 示例: T(n) = 10 n^3
对于算法的时间效率,我们可以用"大O记法"来表示
“大O记法”: 对于单调的整数函数f,如果存在一个整数函数g和实常数c>0,使得对于充分大的n总有f(n)<=cg(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).
如何理解"大O记法"
对于算法进行特别具体的细致分析虽然很好,但在实践中的实际价值有限,对于算法的时间性质和空间性质,最重要的是数量级和趋势,这些是分析算法效率的主要部分.而计量算法基本操作数量的规模函数中的那些常量因子可以忽略不计.
最坏时间复杂度
分析算法时,存在几种可能的考虑:
算法完成工作最少需要多少基本操作,即最优时间复杂度
算法完成工作最多需要多少基本操作,即最坏时间复杂度
算法完成工作平均需要多少基本操作,即平均时间复杂度
对于最优时间复杂度,其价值不大,因为它没有提供什么有用的信息,其反映的只是最乐观最理想的情况,没有参考价值
对于最坏时间复杂度,提供了一种保证,表明算法在此种程度的基本操作中一定能完成工作
对于平均时间复杂度,是对算法的一个全面评价,因此它完整全面的反应了这个算法的性质,但另一方面,这种衡量没有保证,不能每个计算都能在这个基本操作内完成,而且,对于平均情况的计算,也会因为应用算法的实例分布并不均匀而难以计算,因此,我们主要关注算法的最坏情况,亦即最坏时间复杂度.
时间复杂度的几条计算规则
1 基本操作,即只有常数项,认为其时间复杂度为O(1)
2 顺序结构,时间复杂度按加法进行计算
3 循环结构,时间复杂度按乘法进行计算
4 分支结构,时间复杂度取最大值
5 判断一个算法的效率时,往往只需要关注操作数量的最高次项,其他次要项和常数项可以忽略
6 在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
空间复杂度
类似于时间复杂度的讨论,一个算法的空间复杂度定为该算法所耗费的存储空间,它也是问题规模n的函数
渐近空间复杂度也常常简称为空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度.
算法的时间复杂度和空间复杂度合称为算法的复杂度.
python内置类型性能分析
timeit模块
timeit模块用来测试一小段python代码的执行速度
class timeit.Timer(stmt=‘pass’,setup=‘pass’,timer=)
参数解释:
Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法,构造方法, Timer(interval,function,args=[],kwargs={}), interval:指定的时间,function:要执行的方法,args/kwargs:方法的参数,Timer从Thread派生,没有增加实例方法.
timer :是测量小段代码执行速度的类
stmt: 参数是要测量的代码语句(statmet)
setup: 参数是运行代码时需要的设置
timer: 参数是一个定时器函数,与平台无关
timeit.Timer.timeit(number=1000000)
Timer类中测试语句执行速度的对象方法.number参数是测试代码时的测试次数,默认为1000000次,方法返回执行代码的平均耗时,一个float类型的秒数.
测试pop操作:从结果可以看出,pop最后一个元素的效率远远高于pop第一个元素
() ()
:n为列表元素的个数,k表示切片的长度
数据结构 列表(O(n)) 字典(O(1)) 集合 元组
列表和字典都可以存储一个班的学生信息,但是想要在列表中获取一名同学的信息时,就要遍历这个列表,其时间复杂度为O(n),而使用字典存储时,可将学生姓名作为字典的键,学生信息做作为值,进而查询时不需要遍历便可快速获取到学生信息,其时间复杂度为O(1)
我们为了解决问题,需要将数据保存下来,然后根据数据的存储方式来设计算法实现进行处理,那么数据的存储方式不同就会导致需要不同的算法进行处理.我们希望算法解决问题的效率越快越好,于是我们就需要考虑数据究竟如何保持的问题,这就是数据结构.
在上面的问题中我们可以选择python中的列表或字典来存储学生信息,列表和字典就是python内建帮我们封装好的两种数据结构.
概念
数据是一个抽象概念,将其进行分类后得到程序设计语言中的基本类型.如:int float char等. 数据元素之间不是独立的,存在特定的关系,这些关系便是结构.数据结构指数据对象中数据元素之间的关系.
python给我们提供了很多现成的数据结构类型,这些系统之间定义好的,不需要我们之间去定义的数据结构叫做python的内置数据结构,比如:列表 元组 字典. 而有些数据组织方式,python系统里面没有直接定义,需要我们之间去定义实现这些数据的组织方式,这些数据组织方式称为python的扩展数据结构,比如栈 队列等.
算法与数据结构的区别
数据结构只是静态的描述了数据元素之间的关系.
高效的程序需要在数据结构的基础上设计和选择算法
程序=数据结构+算法
总结: 算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体
抽象数据结构
抽象数据类型(ADT)的含义是指一个数学模型以及定义在此数学模型上的一组操作.即把数据类型和数据类型上的运算捆在一起,进行封装. 引入抽象数据类型的目的是把数据类型的表示和数类型上运算的是实现与这些数据类型和运算在程序中的运用隔开,是它们相互独立.
最常用的数据运算有五种: 插入 删除 修改 查找 排序
顺序表
在程序中,经常需要将一组数据类型作为整体管理和使用,需要创建这种元素组,用变量来记录它们,传进传出函数等.一组数据中包含的元素个数可能发生改变(可以增加或删除元素).
对于这种需求,最简单的解决方案便是将这样一组元素看成一个序列,用元素在序列里的位置和顺序,表示实际应用中的某种有意义的信息,或者表示数据之间的某种关系.
这样一组序列元素的组织形式,我们可以将其抽象为线性表.一个线性表是某类元素的一个集合,还记录着元素之间的一种顺序关系.线性表是最基本的数据结构之一,在实际程序中应用非常广泛,它还经常被用作最复杂的数据结构的实现基础.
根据线性表的实际存储方式,分为两种实现模型:
顺序表,将元素顺序地放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示.
链表 , 将元素存放在链接构造起来的一系列存储块中.
表a表示的是顺序表的基本形式,数据元素本身连续存储,每个元素所占用的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址Loc(eO)加上逻辑地址(第i个元素)与存储单元大小©的乘积计算而得,即: Loc(ei)=Loc(eO)+c*i
故,访问指定元素时无需从头遍历,通过计算便可获得对应地址,其时间复杂度为O(1)
如果元素的大小不统一,则须采用图b的元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保持对应元素的地址信息(即链接).由于每个链接所需要的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素.注意,图b中的c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量很小. 图b这样的顺序表也被称为对实际数据的索引,这是最简单的索引结构.
顺序表的两种基本实现方式
图a为一体式结构,存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象.
一体式结构整体性强,易于管理.但是由于数据元素存储区域是表对象的一部分, 顺序表创建后,元素存储区就固定了.
图b为分离式结构,表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联.
元素存储区替换
一体式结构由于顺序表信息区域数据去连续储存在一起,所以若想更换数据区,则只能整体搬移,即整改顺序表对象(指存储顺序表的结构信息的区域)改变了.
分离式结构若想更欢数据区,只需将表信息区中的数据区链接地址更新即可,而该顺序表对象不变.
元素存储区扩充
采用分离式结构的顺序表,若将数据区更欢为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行扩充,所有使用这个表的地方都不必修改,只要程序的运行环境(计算机系统)还有空闲存储,这种表结构就不会因为满了而导致操作无法进行,人们把采用这种技术实现的顺序表称为动态顺序表,因为其容量可以在使用中动态变化.
扩充的两种策略
每次扩充增加固定数目的储存位置,如每次扩充增加10个元素位置,这种策略可以称为线性增长. 特点: 节省空间,但是扩充操作频繁,操作次数多.
每次扩容容量加倍,如每次扩充增加一倍存储空间 特点: 减少了扩充操作的执行次数,但可能会浪费空间资源.以空间换时间,推荐的方式.
增加元素
python中的顺序表
python中的list和tuple两种类型采用了顺序表的实现技术,具体前面讨论的顺序表的所有性质.
tuple是不可变类型,即不变的顺序表,因此不支持改变其内部状态的任何操作,而其他方面,则与list的性质类似.
list的基本实现技术
位运算 往右移位 8 >> 2==> 8/2的2次方 往左移位 8 << 2 ==> 8*2的2次方
python标准类型list就是一种元素个数可变的线性表,可以加入和删除元素,并在各种操作中维持已有元素的顺序(即保序),而且还具有以下行为特征:
基于下标(位置)的高效元素访问和更新,时间复杂度应该是O(1);为满足该特征,应该采用顺序表技术,表中元素保存在一块连续的存储区中.
运行任意加入元素.而且在不断加入元素的过程中,表对象的识别(函数id得到的值)不变. 为满足该特征,就必须能更换元素存储区,并且为保证更换存储区时list对象的标识id不变,只能采用分离式实现技术.
在python的官方实现中,list就是一种采用分离技术实现的动态顺序表,这就是为什么用list.append(x) (或 list.inser(len(list’),x) 即尾部插入 ) 比在指定位置插入元素效率高的原因.