1.什么是数据结构?
答:存储、组织数据的方式。数据的种类有很多:字符串、整数、浮点、...
组织各种数据的方式:即数据元素之间的关系:列表、字典、元组、...
举例:
列表方式: [老王,18,男]
字典方式:{name:"老王",age:18,sex:"男"}
综合方式:[{name:"老王",age:18,sex:"男"},{name:"老李",age:19,sex:"男"}]
{老王:{age:18,sex:"男"},老李:{age:19,sex:"男"}}
数据的结构有两种形式:
物理形式:顺序表、链表
逻辑形式:集合、线性、树形、图形
2.算法是什么?
为了实现业务目的各种分析方法和思路就是算法
公司的核心:数据
数据的存储:数据结构:
存储数据的目的:提供信息和解决问题
算法复杂度:
时间复杂度:代码执行需要的计算工作量
空间复杂度:代码运行需要的内存空间
业务数据:
用户访问业务时候,产生的信息内容
数据结构:
静态的描述了数据元素之间的关系
算法:
解决各种实际问题的方法和思路
数据结构 + 算法 = 程序
抽象数据类型(Abstract Data Type)
抽象数据类型(ADT)的含义是指一个数学类型以及定义在此数学类型上的一组操作。即把数据类型和数据类型上的运算捆在一起,进行封装。
引入抽象数据类型的目的:
是把数据类型的表示和运算的实现与这两者在程序中的引用隔开,使它们相互独立。
简单来说:就是把数据带入场景,让他们“有意义”
常见数据运算类型:
插入、删除、修改、查找、排序,即所谓的“增删改查”
思路总结:算法特性
1、输入: 算法具有0个或多个输入
2、输出: 算法至少有1个或多个输出
3、有穷性: 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成
4、确定性:算法中的每一步都有确定的含义,不会出现二义性
5、可行性:算法的每一步都是可行的,也就是说每一步都能够执行有限的次数完成
算法的评判标准:
问:为什么不一样?哪个方法好,时间慢的方法就好么?
答:1、因为不一样,所以不一样。
2、不一定
计算依赖于服务器环境,环境不唯一,所以用时间来评判,不准确。
问: 如何综合评判两种方法?
分析:至少把代码运行的环境因素抛离出去,就剩下代码本身,代码本身使我们解决思路的实现,也就是算法
那么我们应该怎么评判算法呢?:
答:代码量太大,没办法整体分析,我们就拆,拆到我们能接收的方式来分析:
评判一个算法是否优秀,我们应该从两个方面来思考方法的优劣:
步骤数量和单位时间
问:步骤数量和单位时间的关系?
步骤数量 * 单步执行时间 == 代码执行总时间
1、算法特性:量入出题题能解,行有意步步可为
2、算法的评判标准:步骤数量+单位时间
时间复杂度的6条基本计算规则
1、基本操作,即只有常数项
简单来说:没有数量规模,就执行一次
时间复杂度:O(1)
2、顺序结构,时间复杂度按加法进行计算
简单来说:一步一步的执行下去,类似上面的方法二
时间复杂度:O(n)
3、循环结构,时间复杂度按乘法进行计算
循环:就是批量执行多次,类似上面的方法一:
时间复杂度:O(n3)
4、分支结构,时间复杂度取最大值
简单来说:就是多分支if语句,找一个时间最长的作为标准的时间
5、判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略
6、在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
常见时间复杂度及函数样式
注意:
2m = n,知道n的值,求m,
m=log2n 而我们经常将log2n(以2为底的对数)简写成logn
所消耗的时间从小到大
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
简单来说:数字最小,n越多,值越大
时间复杂度越小,效率越高,时间越短
timeit模块
timeit模块可以用来测试一小段Python代码的执行速度。
核心代码1介绍
class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)
代码详解:
Timer是测量小段代码执行速度的类。
stmt参数是要测试的代码语句(statment)
例子:测试某个函数,函数代码用"func()"表示
setup参数是运行代码时需要的设置;
例子:测试当前文档的func模块,使用 "from __main__ import func"
timer参数是一个定时器函数,与操作系统平台有关,一般省略。
示例:
timer1 = timeit.Timer("T1()","from __main__ import T1")
核心代码2介绍
timeit.Timer.timeit(number=1000000)
代码详解:
Timer类中测试语句执行速度的对象方法。
number参数是测试代码时的测试次数,默认为1000000次。
timeit方法返回执行代码的总共耗时,它是一个float类型的秒数。
简写方法:测试代码.timeit(1000000)
示例:
timer1.timeit(number=1000) 简写 timer1.timeit(1000)
timeit模块使用原则:生成测试对象,测试效果
数据结构 进阶
线性表简介:
顾名思义:就是将一大堆同类型的数据,按照某种关系,依次排列出来,就形成了一条线。
特点:数据之间,只有前后关系。
举例:
100 200 300 400 ...... n
线性表是最基本,也使用最广的数据结构之一,常被用作更复杂的数据结构的实现基础。
线性表分类:
根据线性表的实际存储方式,分为两种常见的模型:
顺序表:
将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
链表:
将元素存放在通过链接构造起来的一系列存储块中。也就是说,这种表不但有自己的数据信息,也有下一个数据结点的地址信息
链表有如下分类:单向链表、双向链表、单向循环链表
顺序表两种形式:
基本布局 + 元素外置
基本布局存储同类数据
元素外置存储异类数据
a)基本布局:
简单来说,就是存储多个同类型的数据,每个数据占用一个固定存储空间,多个数据组合在一块的时候,物理地址就会连续起来
b) 元素外置
元素外置,适用于存储不同类型的数据A-D,因为不同类型数据的占用存储空间不一样,不能按照基本布局的方式来存储。
通过比较发现,虽然他们数据占用的空间不一致,但是他们的逻辑地址编号都是同一的数据类型,所以存储他们的时候,可以单独申请一块连续空间,来存储A-D数据的逻辑地址。
图b这样的顺序表,因为顺序表B内存储的是上面顺序表A的逻辑地址数值,通过存储的逻辑地址,找到真正存储数据的地址,所以顺序表B中的数值也被称为对实际数据的索引,这是最简单的索引结构。
顺序表结构
现在我们介绍一下顺序表的结构
顺序表结构
通过上图的介绍,我们可以知道顺序表由两部分组成:基本信息+存储元素。
因为顺序表有两中表现形式,所以就出现了两种表现形式:
基本布局:一体式结构
元素外置:分离式结构
整合式 或 一体式
一体式结构:
存储表信息与元素存储区以连续的方式安排在一块存储区里,形成一个完整的顺序表对象。
一体式特点:
因为整体,所以易于管理,顺序表创建后,元素存储区就固定了。
分离式
分离式结构:
顺序表对象里保存基本信息(地址容量+元素个数)和元素存储空间的一个逻辑地址(A),通过逻辑地址(A)找到真正的数据存储地址。
分离式特点:
因为分离,所以灵活,元素的存储空间可以灵活调整
---------
3.2.3 顺序表实践
我们在设定数据表的时候,会首先申请我要使用的存储空间。而存储空间中的数据不是一成不变的,接下来我们来介绍一下元素存储内容变化时候顺序表的变化。
存储内容的变化,无非是少或多,少会造成存储空间的浪费,多的话,存储空间就会发生变化。我们接下来主要是对存储内容更改或者存储内容大于计划空间的这两种情况进行分析。
内容更改
一体式:
因为一体式,基本信息和存储元素是一个整体的对象,而且一旦定义,就不会在更改了。
存储内容一旦大于计划容量,若要存储更多内容,只能整体搬迁。即整个顺序表对象(指存储顺序表的结构信息的区域)改变了
示例:
计划8个容量,存储4个值
更改最后一个数据的内容d为e
因为存储数据变化,所以顺序表对象就变动了
分离式:
因为分离式,基本信息和存储元素是两个独立的对象,他们彼此间使用基本信息后面的逻辑地址来连接,所以存储数据变动,只不过是存储的逻辑地址变动,而存储对象中的基本信息没有发生变动,所以,对于顺序表对象来说,没有发生变动
示例:
计划8个容量,存储4个值
更改最后一个数据的内容d为e
通过上面对于两种顺序表结构的内容更新实践分析,数据发生变动,我们推荐使用分离式顺序表结构,为什么?因为对象没有变动,一体式相当于重新开辟了一片空间,产生了一个新的顺序表对象,没有分离式省事简便。
所以接下来分析数据内容增加的时候,我们只针对分离式顺序表结构。
空间扩容
采用分离式结构的顺序表,可以在不改变表对象的前提下,将数据存储区更换为存储空间更大的区域,所有使用这个顺序表的地方都不必修改。只要程序的运行环境(计算机系统)还有空闲存储,这种表结构就不会因为数据存储区空间不足满了而导致操作无法进行。人们把采用这种技术实现的顺序表称为动态顺序表,因为其容量可以在使用中动态变化。
目前动态顺序表的扩容策略分为两大类:线性增长、倍数增长
线性增长:
数据存储区容量一旦发现不足,每次扩充增加固定数目的存储区容量,如每次扩充增加4个元素位置。
特点:
确定性:因为增加数量有限制,所以节省空间,
不确定性:扩充操作可能频繁发生、随时发生。
倍数增长:
数据存储区容量一旦发现不足,每次扩充容量加倍,如:
当前容量8,容量不足时候,就扩充8,最终容量为16。
当前容量16,容量不足时候,就扩充16,最终容量为32。
...
如此循环往复,每次扩充的容量都是当前的容量空间大小。
特点:
确定性:每次增加容量会很大,所以扩充操作的执行次数不会频繁发生
不确定性:容量扩充答,可能会造成空间资源浪费。
根据上面对于动态顺序表的容量增加分析,可以知道,倍数增长方式是以空间换时间,因为目前空间代价越来越低廉,所以我们工作中推荐使用倍数增长的方式来调整容量空间。
隐含意思:单一元素的值就是顺序表 -- 后续所有知识点都会用到这一个认知
内容总结:
1、顺序表形式:基本布局(同类数据)+元素外置(异类数据)
2、顺序表结构:一体式+分离式(灵活)
3、顺序表容量扩充策略:线性(频繁)+倍增
4、顺序表元素增加:头[保序、O(n)]+尾[保序、O(1)]+位置[非保序、O(1)]
5、顺序表元素删除:头[保序、O(n)]+尾[保序、O(1)]+位置[非保序、O(1)]