前言
课程内容
重要性体现
图灵奖Pascal语言之父——Nicklaus Wirth,提出一个公式:程序 = 数据结构+算法
这个公式就体现程序的本质,公式里面有数据结构,也足以见得数据结构和算法的重要性。
数据结构是计算机软件中专业的基础课,属于核心、承上启下的作用。
换到修仙里面,就是闭关修炼、练内功,内功修炼的过程中都是比较煎熬的,需要静下心来逐点突破。
具体的学科学习流程如下:
如何学好数据结构
- 勤于思考,发散思维。
- 多做练习,熟能生巧。
- 寻求帮助,不能得过且过。
- 不怕困难,不放弃。
数据结构的研究内容
引入
通常计算机解题一个问题的步骤:
- 具体问题,然后抽象为数学模型:如何抽象为数学模型呢?首先分析问题,然后提取操作对象,找出操作对象之间的关系,用数学语言描述
- 明确基本步骤,设计算法实现。
- 编程、调试和运行。
早期计算机研究的内容
早期计算机主要用于数值计算,比如求解向量,求解微分方程等
这些需求的特点是:
- 元素之间的关系简单
- 元素之间涉及的计算复杂
现代计算机研究的内容
随着计算机不断扩展,也用于在非数值之间的计算,主流是处理非数值对象之间的关系
比如说,开发一个学生管理系统,管理学生的信息还有涉及学生的其他对象
常见的需求是:添加一个学生、删除一个学生、编辑学生的信息、查找一个学生并后续需求做准备等。
上面就涉及:
- 操作对象:每位学生的信息(姓名、学号、性别、出生年月日、籍贯、专业)
- 操作算法:查询、插入、修改和删除等
- 操作对象之间的关系:线性关系,映射到数据结构就是线性表
还有就是人机博弈问题、涉及游戏领域、智慧城市等,涉及的对象各不相同,对于操作对象之间的关系也各不相同,比如非线性结构(树、图)等,这些数据都是计算机无法用数学公式或者方程来描述的。
这都是当下计算机需要考虑如何解决的问题。
这就需要数据结构
数据结构:一门研究非数值计算的程序设计中计算机的操作对象以及它们之间的关系和操作的学科
基础概念
数据
既然你要用计算机处理数据,就要把数据抽象到计算机中,那么数据也要规范定义起来
定义:
- 是能够输入到计算机中的各种符合集合
- 是信息的载体
- 能够被计算机识别
- 计算机能够对其进行处理
符合集合主要分为以下部分:
- 数值型数据:整数、实数等
- 非数值型数据:文字、图像、音频等
数据元素
对于有些数据之间关系比较紧密,所以操作的时候会对它们整体进行操作
比如说:学生管理系统对某一位学生的信息进行修改,那么该学生的相关信息就会作为一个整体进行处理,如果我现在要删除一个学生,就要把该学生的记录包含的信息(姓名、班级、年龄、性别等)删除
所以,数据元素定义如下:
- 是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理
- 也称之为结点、记录、元素等
数据项
构成数据元素不可分割的一部分,比如数据元素举的例子来说,一个学生的姓名就是学生数据对象不可分割的基本项
数据、数据元素、数据项关系
数据 > 数据元素 > 数据项
数据对象
数据对象是数据元素性质相同的集合,是数据的一个子集。
例如:
整数数据对象是集合N={0,±1,±2,±3,…,±N}
字母字符数据对象是集合C={‘A’,‘B’,‘C’,…,‘Z’}
学籍表也是数据对象
数据元素与数据对象
数据元素
- 组成数据的人体
数据对象
- 数据的子集
举例子来说:
现在有两批人,一批是不读书的,一批是读书的,它们都是社会的数据元素,但是由于它们性质不同,所以一批大部分放在学校中,另一个只能在社会中,它们是不同的数据对象,即集合交集为空。
数据结构
定义
数据元素并不是孤立存在的,它们之间存在着某种关系,数据元素相互之间的关系称为结构。
结构:是指相互之间存在一种或者多种特定关系的数据元素集合,也可以说带结构的数据元素的集合。
要素
- 数据元素之间的逻辑关系,称之为逻辑结构
- 数据元素及其关系在计算机内存中的表示,称之为物理结构
- 数据的运算和实现,首先从逻辑分析是否可行,然后落实到物理结构中实现
解析逻辑结构
详细阐述
- 描述数据元素之间的关系
- 与数据存储无关,独立于计算机
- 是从具体问题抽象出来的数学模型
分类
基于线性或者非线性划分
- 线性结构:有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前驱和一个直接后继,比如:线性表、栈、队列、串。
- 非线性结构:一个结点可能有多个直接前驱和直接后继,比如:树、图。
四种基本逻辑结构
-
集合结构:结构中的数据元素除了同属一个集合之外,无其他任何关系。
-
线性结构:结构中的数据元素存在一对一线性关系。
-
树形结构:结构中的数据元素存在一对多的非线性层次关系。
-
图状结构:结构中的数据元素存在多对多的非线性任意关系。
解析物理结构
详细阐述
- 是数据元素及其关系在计算机存储器如何存储实现的方式
- 是数据元素在计算机中的表示
分类
-
顺序存储结构:用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素存储位置决定,C语言中使用数组实现。
-
链式存储结构:用分散的存储单元来存储数据元素,这些存储单元可能是连续的,可能是分散的,而数据元素之间的逻辑关系由存储单元里面的指针存储的下一个数据元素地址来实现,C语言使用指针表示。
-
索引存储结构:在存储结点的时候,还建立附加的索引表,比如手机里面的通讯录,
可以通过索引表,快速定位具体的数据元素。
-
散列存储结构:
根据结点的关键字直接计算出该结点的存储地址
逻辑结构与物理结构
- 存储结构是逻辑关系的映射与元素本身的映像
- 逻辑结构是物理结构的抽象,存储结构是数据结构的实现
数据类型和抽象数据类型
数据类型
在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量、常量或者表达式,明确说明它们所属的数据类型
例如,C语言中:
- 提供int、float、double等基本数据类型
- 数据、结构、共用体、枚举等构造数据类型
- 指针、空
- 用户自定义的typedef数据类型
那么定义了这些数据类型之后,有什么用?
定义数据类型之后,相当于明显的规定了程序执行的过程中变量和表达的所有可能的取值范围,以及在这些数值上所允许进行操作。
比如:在C语言中的int类型,就是就是int对应的数据只能在[-min,max]上进行处理,可以进行+、-、*、/等操作。
规范的定义: 是一组性质相同的值的集合及其定义于这个值集合上的一组操作的总称。
抽象数据类型(ADT)
定义
抽象:从具体的事物中、概括出它们的共同方面、本质属性与关系,而将个别的、非本质的、属性与关系舍弃,这种思维过程就叫做抽象。
举例子:
忽略每个圆形的大小、颜色、粗细,只关注共同方面、本质属性与关系
- 圆:是每个点距离指定点相同距离的图形
- 运算:构造圆、求面积、求周长
而抽象数据类型指的是一个数学模型以及定义在此数学模型上的一组操作
- 由用户定义,从问题抽象出数学模型(逻辑结构)
- 还包括定义在数学模型上的一组抽象运算(相关操作)
- 不考虑计算机内的具体存储与运算的具体实现的算法
形式定义
抽象数据类型可用(D,S,P)三元组表示
- D是数据对象
- S是D上的关系集
- P是对D的基本操作集
定义格式
ADT 抽象数据类型名{
数据对象:<数据对象的定义>,
数据关系:<数据关系的定义>,
基本操作:<基本操作的定义>
}ADT 抽象数据类型名
- 数据对象和关系用伪代码表示
- 基本操作的定义格式为:基本操作名(参数列表,参数需要返回前面添加一个&符号)、初始条件<初始条件描述>、操作结果<操作结果描述>
初始条件:描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息
操作结果:说明操作正常情况会怎么样,操作异常又是怎么样并返回相应的结果。
示例:
ADT Circle{
数据对象:D={r,x,y|r,x,y均为实数}
数据关系:R={<r,x,y>|r表示半径,<x,y>是圆心坐标}
基本操作:
1.Circle(&C,r,x,y)
1.1初始条件:参数符合条件且不为空
1.2操作结果:构造圆
2.double Area(C):
2.1 初始条件:圆已存在
2.2 操作结果:返回圆的面积
3.double Circumference(C):
3.1初始条件:圆已存在
3.2操作结果:返回圆的周长
}
作用
定义完抽象数据类型之后就可以映射为具体的编程语言进行实现。
总结逻辑、物理结构及其算法的关系
- 逻辑结构:研究对象的特性及其相互之间的关系
- 存储结构:有效的将数据元素存储到计算机中,并且保持逻辑结构的关系
- 算法:高效的对存储到计算机中的数据进行具体的需求运算
算法
定义
对特定问题求解方法和步骤的一种描述,它是指令的有限序列,其中每个指令表示一个或者多个操作。
描述
- 自然语言
算法,求一元二次方程的根
1、输入方程的系数a、b、c。
2、判断a是否等于零。如果等于零,则提示不是一元二次方程。不等于零,则执行第3步。
3、计算d=b2-4ac
4、判断d。如果d等于零,计算并输出两个相等实根。如果d小于零输出没有实根。如果d大于零。输出两个不等实根。
5、结束
- 流程图或者NS图
- 伪代码:有一定的编程语言和自然语言进行描述,不完全符合语言
算法与程序
- 算法是解决问题问题的一种方法或过程,考虑如何将输入转换为输出,一个问题可以有多个算法
- 程序使用某种程序设计语言对算法的具体实现,算法操作的对象是数据结构
算法的特性
记忆口诀:有定性01
- 有穷性:一个算法的步骤总是在执行有穷步之后结束,且每一步都在有穷时间内完成。
- 确定性:算法中的每一个步骤必须有确切的含义,没有二义性,在任何情况下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
- 可行性:算法是可执行、可实现的。算法描述的操作可以通过已经实现的基本操作执行有限次来实现。
- 输入:至少0个或以上输入
- 输出:至少1个或以上输出
好的算法设计
记忆口诀:正可健效
- 正确性:程序不含有语法错误、对于精心挑选的测试数据能够通过、对于一切合法的输入数据都能得到结果
- 可读型:算法主要是为了人的阅读和交流,其次才是为了计算机可执行,另一方面,晦涩难懂的算法易于隐藏较多的错误和难以调试
- 健壮性(鲁棒性):对于一些错误的输入,算法能够给出相应的特殊处理,避免程序执行崩溃
- 效率:对于一个问题的求解有很多方法或者步骤,那么计算机的资源是有限的,如何在有限的资源内,程序在空间和执行时间两者之间进行平衡,因为空间和时间程序不能兼得。
时间复杂度
如何度量时间
- 事后统计:将算法实现,测试运行时间和空间开销,
- 事前分析:对算法所消耗的资源的一种估算
一般采用事前统计法,因为事后统计需要考虑的因素太多了,如电脑配置、高级语言等,可能硬件好掩盖了算法的弊端,可能硬件差,导致本来好的算法效果差。
事前分析法
一个算法时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单操作所需要的时间与算法中进行的简单操作次数的乘积。
所以程序总的执行时间就是sum(每条语句执行次数x该语句的执行一次所需的时间)
每条语句执行也称之为语句频度
每条语句执行一次所需要的时间,一般由机器性能差异决定,这是客观的,与算法无关,所以客观分析算法就可以将该语句执行一次的时间理想为单位时间,那么接下来就执行次数进行讨论就可以得出算法的时间性能。
执行次数:一般取该算法中执行频度最高的语句(因为影响时间最大的因素是它)来进行分析,其他的语句在n → ∞的时候可以忽略,其中n表示问题的规模。
业界用大O表示法来进行分析
语句频度之和O(f(n)) ~ T(n),在n → 无穷的时候,这里涉及极限的概念,由于我们只关注时间的数量级,所以不是主体的都可以进行忽略(忽略低次项和最高幂的次数),还有使用加法规则和乘法规则。
- 加法规则:T(n)=T1(n)+T2(n)=O(f1(n))+O(f2(n))=O(max(f(n),g(n))
- 乘法规则:T(n)=T1(n)xT2(n)=O(f1(n))xO(f2(n))=O(f(n)xg(n))
分析步骤
- 找出语句频度最高的语句作为基本语句
- 计算基本语句的频度得到问题规模n的某个函数f(n)
- 取数量级符号O表示
但是基本语句的执行次数还跟问题的输入情况不同而次数不同
比如:算法使用一个顺序查找一个元素
如果要查找的元素就在第一位,则只执行一次
如果要查找的元素就在第一位,则只执行N次
这些最好和最坏的情况参考的意义不大,我们拿平均情况来分析
比较时间性能
常数阶 > 对数阶 > 线性阶 > 对数线性阶 > 平方阶 > 立方阶 > K阶 > N的N阶 > 指数阶
总结
涉及时间复杂度的要素:
- 基本语句执行次数
- 问题的模块
- 问题的初始状态
空间复杂度
如何度量
采用渐进空间复杂度,算法所需要存储的空间容量,记作S(n)=O(f(n)),其中n为问题的规模
算法要占据的空间也是采用考察最大的程序所需要的空间资源
其中递归且包含数组的情况,空间复杂度最大。
对比
常数阶 > 对数阶 > 线性阶 > 对数线性阶 > 平方阶 > 立方阶 > K阶 > N的N阶 > 指数阶