1.2 基本概念和术语
1.2.1 数据、数据元素、数据项和数据对象
数据:
能够输入计算机且能够被计算机处理的各种符号的集合
- 信息的载体
- 是对客观事物符号化的表示
- 能够被计算机识别、存储和加工
包括:
- 数值型数据:整数、实数等
- 非数值型数据:文字、图像、图形、声音等
数据元素:
是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
也简称为元素,或记录、节点、顶点
数据项:
构成数据元素的不可分割的最小单位
💡 三者直接的关系:
数据>数据元素>数据项
例:学生表>个人记录>学号、姓名……
数据对象;
是性质相同的数据元素的集合,是数据的一个子集。
如:
- 整数数据对象是集合N={0,1,-1,2,-2,……}
- 字母字符数据对象是集合C={‘A’,’B’,……,’Z’}
- 学籍表也可看作一个数据对象
💡 数据元素与数据对象的关系:
数据元素——组成数据的基本单位
与数据的关系:是集合的个体
数据对象——性质相同的数据元素的集合
与数据的关系是:集合的子集
1.2.2 数据结构
- 数据元素不是孤立存在的,它们之间存在着某种关系,数据元素相互之间的关系称为结构
- 是指相互直接存在一种或多种特定关系的数据元素集合
- 或者说,数据结构是带结构的数据元素的集合
数据结构包括以下三个方面的内容:
- 数据元素之间的逻辑关系,也称为逻辑结构。
- 数据元素及其关系在计算机内存中的表示(又称为映像),称为数据的物理结构或数的存储结构。
- 数据的运算和实现,即对数据元素可以施加的操作以及这些操作在相应的存储结构上的实现。
数据结构的两个层次 :
- 逻辑结构
- 描述数据元素之间的逻辑关系
- 与数据的存储无关,独立于计算机
- 是从具体问题抽象处理的数学模型
- 物理结构(存储结构)
- 数据元素及其关系在计算机存储器中的结构(存储方式)
- 是数据结构在计算机中的表示
- 逻辑结构与存储结构的关系:
- 存储结构是逻辑关系的映像与元素本身的映像
- 逻辑结构是数据结构的抽象,存储结构是数据结构的实现
- 两者综合起来建立了数据元素之间的结构关系
逻辑结构的分类:
划分方法一:
- 线性结构
有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前趋和一个直接后继。
如:线性表、栈、队列、串 - 非线性结构
一个结点可能有多个直接前趋和直接后继
例如:树、图
划分方法二:——四类基本逻辑结构
-
集合结构
结构中的数据元素之间除了同属于一个集合的关系外,无任何其他关系
-
线性结构
结构中的数据元素之间存在着一对一的线性关系
-
树形结构
结构中的数据元素之间存在着一对多的层次关系
-
图状结构或网状结构:
结构中的数据元素之间存在着多对多的人员关系
存储结构的分类:
-
顺序存储结构
- 用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示
- C语言中用数组来实现顺存储结构
-
链式存储结构
- 用一组任意的存储单元数据元素,数据元素之间的逻辑关系用指针来表示。
- C语言中用指针来实现链式存储结构
- 每个结点占用两个连续的存储单元,一个存放结点的信息,另一个存放后继结点的首地址(没有了就存储Null或^)
-
索引存储结构
- 在存储结点信息的同时,还建立附加的索引表。
- ………………
-
散列存储结构
- 根据结点的关键字之间计算出该结点的存储地址
- ………………
1.2.3 数据类型和抽象数据类型
数据类型
- 在使用C语言编写程序时,必须对程序中出现的每个变量、常量或表达式,明确说明它们所属的数据类型
- 如,在C语言中:
- 提供int,char,float,double等基本数据类型
- 数组,结构,共用体,枚举等构造数据类型
- 还有指针,空(void)类型
- 用户也可用typedef自己定义数据类型
- 如,在C语言中:
- 一些最基本数据结构可以用数据类型来实现,如数组、字符串等;
- 定义:数据类型是一组性质相同的值的集合以及定义于这个值集合上的一组操作的总称
💡 数据类型=值的集合+值集合上的一组操作
抽象数据类型(ADT)
从具体事物、概括出它们共同的方面,本质属性与关系等,而将个别的,非本质的方面、属性与关系舍弃,这种思维过程,称为抽象。
-
举例解释:
此图中画的是圆,这就是抽象;
我们会看到它的本质为圆,而忽略掉它的非本质的东西,如大小,颜色,粗细,空心。
抽象处理的概念为:
圆——到某个点的距离相等的点的集合
运算——构造圆,求面积,求周长
是指一个数据模型以及定义在此数据模型上的一组操作
- 由用户定义,从问题抽象出数据模型(逻辑结构)
- 还包括定义在数据模型上的一组抽象运算(相关操作)
- 不考虑计算机内的具体存储结构与运算的具体实现算法
抽象数据类型的形式定义
💡 抽象数据类型可用(D,S,P)三元组表示。
D是数据对象
S是D上的关系集
P是对D的基本操作集
抽象数据类型定义格式:
💡 ADT 抽象数据类型名{
数据对象:<数据对象的定义> // 用伪代码描述
数据关系:<数据关系的定义> // 用伪代码描述
基本操作:<基本操作的定义>
}ADT 抽象数据类型名
基本操作的定义格式为:
- 基本操作名(参数表)
- 参数表:赋值参数——只为操作提供输入值
- 引用参数——以&打头,除可提供输入值外,还将返回操作结果给赋值给自身
- 初始条件:(初始条件描述)
- 描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。若初始条件为空,则省略之。
- 操作结果:(操作结果描述)
- 说明操作正常完成之后,数据结构的变化状况和应返回的结果
ADT定义举例:
- 格式
1.1和1.2总结
1.3 抽象数据类型的表示与实现
C语言实现抽象数据类型——>就可以在程序中使用
- 用已有数据类型定义描述、它的存储结构
- 用函数定义描述它的操作
实现:
抽象数据类型可以通过固有的数据类型(如整形、实型、字符型等)来表示实现
-
即利用处理器中已存在的数据类型来说明新的结构,用已经实现的操作来组合新的操作。
取指针结构型变量的成员用”- >”
去普通结构型变量的成员用 “.” -
举例
函数直接调用上张图中的
1.4 算法与算法分析
算法定义:
- 对特定问题求解方法和步骤的一种描述,它是指令的有限序列。其中每个指令表示一个或多个操作。
- 简而言之,算法就是解决问题的方法和步骤
- 举例
算法:求一元二次方程的根:
算法的描述
- 自然语言:英文,中文
- 流程图:传统流程图,NS流程图
- 伪代码:类语言:类C语言
- 程序代码:C,JAVA……
算法与程序
- 算法是解决问题的一种方法或一个过程,考虑如何将输入转换成输出,一个问题可以有多种算法
- 程序是用某种程序设计语言对算法的具体实现
💡 程序=数据结构+算法
数据结构通过算法实现操作
算法根据数据结构设计程序
算法特性
一个算法必须具备以下五个重要特性
- 有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在又穷时间内完成
- 确定性:算法章的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
- 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现
- 输入:一个算法有零个或多个输入。
- 输出:一个算法有零个或多个输出。
算法设计要求
- 正确性
- 可读性
- 健壮性
- 高效性
算法分析
算法效率
- 时间效率:指的是算法所耗费的时间
- 空间效率:指的是算法执行过程中所耗费的存储空间
💡 时间效率和空间效率有时候是矛盾的
算法时间复杂度
算法时间效率可以用依据该算法编制的程序在计算机上执行所消耗的时间来度量。
两种度量方法
- 事后分析
- 将算法实现,测算其时间和空间开销
- 缺点:编写程序实现算法将花费比较多的时间和精力;所得实验结果依赖于计算机的软硬件等环境因素,掩盖算法本身的优劣
- 事前分析
- 对算法所消耗资源的一种估算方法
事前分析方法
- 一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数乘积
💡 算法运行时间=一个简单操作所需的时间*简单操作次数
- 也即算法中每条语句的执行时间之和
💡 算法运行时间=∑每条语句的执行次数该语句执行一次所需的时间
每条语句的执行时间< = >每条语句频度
算法运行时间=∑每条语句频度该语句执行一次所需的时间
每条语句执行一次所需的时间,一般是随机器而异的。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,它与算法无关。
所以,我们可假设执行每条语句所需的时间均为单位时间。此时对算法的运行时间的讨论就可转换为讨论该算法中所有语句的执行次数,即频度之和了。
这就可以独立于不同机器的软硬件环境来分析算法的时间性能了。
// 两个n*n矩阵相乘的算法可描述为:
for(i=1;i<=n;i++){ //运行n+1次
for(j=1;j<=n;j++){ //运行n(n+1)次
c[i][j]=0; //运行n*n次
for(k=0;k<=n;k++){suna
我们把算法所耗费的时间定义为该算法中每条语句的频度之和,则上述算法的时间消耗T(n)为:
T ( n ) = 2 n 3 + 3 n 2 + 2 n + 1 T(n)=2n^{3}+3n^{2}+2n+1 T(n)=2n3+3n2+2n+1
算法时间复杂度的渐近表示法
-
为了便于比较不同算法的时间效率,我们进比较它们的数量级(数量级越大的越不好)
- 数量级:即指数值
-
如:两个不同的算法,时间消耗分别是:
T 1 ( n ) = 10 n 2 与 T 2 ( n ) = 5 n 3 T_{1}(n)=10n^{2}与T_{2}(n)=5n^{3} T1(n)=10n2与T2(n)=5n3——————其中T1的数量级为 n 2 n^{2} n2,T2的数量级为 n 3 n^{3} n3,故T2不如T1
定理1.1
若 f ( n ) = a m n m + a m − 1 n m − 1 + … … + a 1 n + a 0 f(n)=a_{m}n^{m}+a_{m-1}n^{m-1}+……+a_{1}n+a_{0} f(n)=amnm+am−1nm−1+……+a1n+a0是m次多项式,则 T ( n ) = O ( n m ) T(n)=O(n^{m}) T(n)=O(nm)
💡 忽略所有低次幂项和最高次幂系数,体现出增长率的含义
- 找出语句频度最大的那条语句作为基本语句
- 计算基本语句的频度得到问题规模n的某个函数f(n)
- 取其数量级用符号“O”表示
//举例
x=0; //执行1次
y=0; //执行1次
for(int k=0;k<n;k++) //执行n+1次
x++; //执行n次
for(int i=0;i<n;i++) //执行n+1次
for(int j=0;j<n;j++) //执行n*(n+1)次
y++; //执行n*n次
//故f(n)=n(n+1)————T(n)=O(n^2)
- 举例
最好,最坏,平均时间复杂度
注意:有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同
//例:顺序查找,在数组a[i]中查找值等于e的元素,返回其所在位置。
for(i=0;i<n;i++)
if(a[i]==e) return i+1; //找到,则返回是第几个元素
return 0;
- 最好情况:1次
- 最坏情况:n
- 平均时间复杂度为O(n) //因为f(n)=n/2;
最坏时间复杂度:指在最坏情况下,算法的时间复杂度
平均时间复杂度:指在所有肯输入实例在等概率出现的情况下,算法的期望运行时间
最好的时间复杂度:指在最好情况下,算法的时间复杂度
- 一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长
对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度:
a) 加法规则
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n))) T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
b) 乘法法则
T ( n ) = T 1 ( n ) ∗ T 2 ( n ) = O ( f ( n ) ) ∗ O ( g ( n ) ) = O ( m a x ( f ( n ) ∗ g ( n ) ) ) T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(max(f(n)*g(n))) T(n)=T1(n)∗T2(n)=O(f(n))∗O(g(n))=O(max(f(n)∗g(n)))
算法时间效率比较
算法的空间复杂度
空间复杂度:算法所需存储空间的度量,记作: S ( n ) = O ( f ( n ) ) S(n)=O(f(n)) S(n)=O(f(n))
其中n为问题的规模(或大小)
算法要占据的空间
- 算法本身要占据的空间,输入/输出,指令,常数,变量等
- 算法要使用的辅助空间
- 例题