绪论
数据结构的三要素是逻辑结构、物理结构和数据运算;算法的性能分析时间复杂度和空间复杂度
数据结构
「数据」是信息的载体,是描述客观事物属性的数、字符和所有能输入到计算机中被识别和处理的符号的集合
「数据元素」是数据的基本单位。「数据项」是数据元素不可分割的最小单位,比如一个学生记录是数据元素相应的数据项就是学号、姓名、性别和年龄;「数据对象」是性质相同的数据元素构成的集合,是数据的子集
「数据类型」是一组性质相同的值(可认为是数据元素)的集合和定义在此集合上的一组操作的总称,值有哪些特性?首先是用二进制保存,其次是有长度限,然后是编码的语义信息,根据这些特征来进行分类,基础数据类型包括字节、字、双字、布尔值、数值和字符等,可以划分为「原子类型」即值不可再分的数据类型、「结构类型」即值可以再分解为若干分量的数据类型和「抽象数据类型」(与在计算机内部如何表示和实现无关)用数据对象、数据关系和基本操作集的三元组定义
「数据结构」是一个数据元素与数据元素间关系的集合,数据元素通常都不是孤立存在的,在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构。数据结构的三要素是「逻辑结构、存储结构和数据的运算」。数据的逻辑结构和存储结构是密不可分的两个方面,算法的设计取决于所选定的逻辑结构,算法的实现依赖于所采用的存储结构
数据类型与数据结构是描述数据的不同角度,不完全平行的不断发展的概念。数据类型描述人的视角下数据的形状(大小与多少)、数据的物理类型(整数、浮点数、字符串、列表和字典等)和数据的变化(加减乘除与增查删改等);数据结构描述人的视角下数据的形状、数据的组织关系(逻辑上抽象的关系与计算机中物理关系)和数据的变化(增查删改等),本身并没有很强的必然关系,不过抽象数据类型的恰好可以完整定义数据结构,因为,一个数据结构的定义取决于所选定的逻辑结构,而一个数据结构的实现依赖于所采用的存储结构,抽象数据类型不关心存储结构,只描述数据的逻辑结构和抽象运算,所以,它不能实现一个数据结构,但可以定义一个数据结构
逻辑结构
数据的逻辑结构是指数据元素之间的逻辑关系,可分为线性结构和非线性结构,抽象于计算机之外。数据元素之间的关系就像一串糖葫芦,从头部的糖葫芦开始串联到尾部的糖葫芦结束,中间的糖葫芦有相互间的前后关系即前驱与后继
![image-20220328145709575](https://i-blog.csdnimg.cn/blog_migrate/428d7186cdfc6a1915b03de7a68e26d1.png)
线性结构
-
数据元素之间存在一对一的有序的关系,存在唯一的一个第一个数据元素,存在唯一的一个最后的数据元素,除最后数据元素之外,其它数据元素均有唯一的后继,除第一数据元素之外,其它数据元素均有唯一的前驱
常见的线性结构有一般线性表、受限线性表(栈、队列和串)、线性表推广(数组)等
非线性结构
集合结构
- 数据元素之间除了同属于一个集合再无其他关系
树形结构
-
数据元素之间存在一对多的关系
常见的树形结构有一般树与二叉树等
图状结构(网状结构)
-
数据元素之间存在多对多的关系
常见的图状结构有有向图与无向图等
存储结构
数据的存储结构也称物理结构是逻辑结构在计算机中的表示,包括数据元素的表示与关系的表示,基于物理存储器件的支撑通过计算机语言的软件实现。当我们设计好数据的逻辑结构后,需要将数据以某种方式存到内存中,此时我们可以选择顺序存储到存储单元连续的内存中,也可以选择其他方式存储到存储单元不连续的内存中,存储结构主要有顺序存储、链式存储、索引存储和散列存储等
顺序存储
逻辑上相邻的数据元素存储在物理位置相邻的存储单元中,元素间的逻辑关系自然的由存储单元的相邻关系体现,因此不需要额外的指针记录数据元素与前驱后继的内存地址
- 优点是可以实现随机存取(获取元素时只需要根据参考元素计算偏移量即可直接存取),缺点是只能使用相邻的整块存储单元,容易产生内存碎片
链式存储
逻辑上相邻的数据元素存储的存储单元的物理位置未必相邻,通过指针记录前驱与后继的内存地址来表示相邻数据元素的逻辑关系
- 优点是避免了内存碎片,充分利用了存储单元,缺点是每个元素都需要存储额外的指针占用了存储空间,且只能顺序存取(存取数据元素时需要从存储的第一个数据元素的结点中的指针开始找到存储的逻辑上相邻的数据元素的节点且不断重复)
索引存储
索引存储分别存放数据元素与数据元素间的关系,存储元素时额外建立索引表(由若干索引项组成)一般形式是关键字或地址,若每个数据元素在索引表中都有一个索引项,则该索引表称「稠密索引」,若一组数据元素在索引表中只对应于一个索引项,则该索引表称「稀疏索引」
- 优点是检索速度快,缺点是建立的索引表占据了额外的时间与存储空间,增加与删除数据时需要同时修改索引表增加了额外的时间
散列存储
根据元素的关键字直接计算出元素的存储地址,也称哈希存储
- 优点是检索、增加和删除都很快,缺点是散列函数不好可能出现元素存储单元的冲突,解决冲突需要增加额外的时间和空间开销
辨析
数据的逻辑结构是面向实际问题的角度出发的,只采用抽象表达方式,独立于存储结构;而数据的存储结构是逻辑结构在计算机上的映射,它不能独立于逻辑结构而存在
顺序表、哈希表和单链表是三种不同的数据结构,既描述逻辑结构,又描述存储结构和数据运算,有序表是指关键字有序的线性表,仅描述元素之间的逻辑关系,它既可以链式存储,又可以顺序存储,循环队列是用顺序表表示的队列,是一种数据结构。栈是一种抽象数据类型,只表示逻辑结构,可采用顺序存储或链式存储
数据运算
数据运算包括了运算的定义与运算的实现。运算的定义针对逻辑结构指出运算的功能;运算的实现针对存储结构指出运算的操作
算法
算法是针对特定问题求解步骤的一种描述,是指令的有限序列,每条指令表示一个或多个操作,具有五个特征,有穷性,有限步之后结束;确定性,不存在二义性没有歧义;可行性,比如受限于计算机的计算能力,有些算法虽然理论上可行,但实际上无法完成;输入,有零或多个能被计算机处理的各种类型数据,如数字,音频,图像等输入;输出,有一至多个与输入有某种特定关系的输出
通常设计一个好的算法需要考虑,正确性,算法能够正确的解决求解的问题;可读性,算法应该能够让人们良好的理解;健壮性,输入非法数据时,算法可以适当的做出正确反应或进行处理而不会产生莫名其妙的输出结果;高性能,时间复杂度和空间复杂度低,数据结构,好的数据结构为算法提供有力支撑
时间复杂度
一个语句的频度指该语句在算法中被重复执行的次数,算法中全部语句的频度之和记为 T ( n ) T(n) T(n) 是算法问题规模 n n n(参数量)的函数,时间复杂度主要分析 T ( n ) T(n) T(n) 的数量级。算法中基本运算的即最深层循环内的语句的频度与 T ( n ) T(n) T(n) 同数量级,因此只需分析最基本的运算的频度 f ( n ) f(n) f(n) 即可,记为 T ( n ) = O ( f ( n ) ) T(n)=O(f(n)) T(n)=O(f(n)),其中 O O O 计算 f ( n ) f(n) f(n) 的数量级等价 T ( n ) T(n) T(n) 的数量级,算法的时间复杂度还取决于输入数据的性质,比如正向遍历查找一个长度为 n n n 的数组 A [ 0 , ⋯ , n − 1 ] A[0,\cdots,n-1] A[0,⋯,n−1] 中的元素 b b b
#include <stdio.h>
/* 函数声明 */
int iadd (int b);
/* 定义全局变量 */
int i = 0;
int n = 10;
int A[10] = {0,1,2,3,4,5,6,7,8,9};
int main ()
{
/* 定义局部变量 */
int b1 = 0;
int b2 = 9;
iadd(b1);
iadd(b2);
return 0;
}
int iadd (int b)
{
/* while 循环执行 */
while(i<=n-1&&(A[i]!=b))
{
i++;
}
/* printf 输出 */
printf("%d\n", i);
return i;
}
若恰好在第一个位置时基本运算 i + + \mathcal{i++} i++ 的频度 f ( n ) = 0 f(n)=0 f(n)=0,时间复杂度为常数阶 O ( 1 ) O(1) O(1),或恰好在最后一个位置时基本运算 i + + \mathcal{i++} i++ 的频度 f ( n ) = n f(n)=n f(n)=n,时间复杂度为线性阶 O ( n ) O(n) O(n),最坏时间复杂度是考虑输入使得 f ( n ) f(n) f(n) 最大,平均时间复杂度是指所有可能输入的 f ( n ) f(n) f(n) 的期望,最好时间复杂度是考虑输入使得 f ( n ) f(n) f(n) 最小,通常时间复杂度指最坏时间复杂度
分析程序的时间复杂度时有两条规则
-
加法规则
T ( n ) = T 1 ( n ) + T 2 ( n ) = O ( f ( n ) ) + O ( g ( n ) ) = O ( m a x ( f ( n ) , g ( n ) ) ) T(n)=T_1(n)+T_2(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)))
-
乘法规则
T ( n ) = T 1 ( n ) ⋅ T 2 ( n ) = O ( f ( n ) ) ⋅ O ( g ( n ) ) = O ( m a x ( f ( n ) ⋅ g ( n ) ) ) T(n)=T_1(n)\cdot T_2(n)=O(f(n))\cdot O(g(n))=O(max(f(n)\cdot g(n))) T(n)=T1(n)⋅T2(n)=O(f(n))⋅O(g(n))=O(max(f(n)⋅g(n)))
循环主体中的变量与循环条件相关,将变量代入循环条件求出循环次数;循环主体中的变量与循环条件不相关,递归程序采用数学归纳法推导循环次数,非递归程序直接累计循环次数
常见的渐进时间复杂度
- O ( 1 ) < O ( l o g 2 n ) < O ( n ) < O ( n l o g 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)<O(log_2n)<O(n)<O(nlog_2n)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n) O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
空间复杂度
一个程序在执行时除需要存储空间来存放本身所用的指令、常数、变量和输入数据,还需要存储空间来存放数据进行的操作与输出数据,算法的空间复杂度 S ( n ) S(n) S(n) 定义为该算法所耗费的存储空间,它是问题规模 n n n(参数量)的函数记为 S ( n ) = O ( g ( n ) ) S(n)=O(g(n)) S(n)=O(g(n))