数据结构第2章 (竟成)
第 2 章 绪论
本章主要介绍数据结构相关的一些基本概念,是后续章节的基础。我们也将 408 考试大纲中,关于数据结构部分的考查目标罗列在这里,供各位考生参考:
1.掌握数据结构的基本概念、基本原理和基本方法。
2.掌握数据的逻辑结构、存储结构及基本操作的实现,能够对算法进行基本的时间复杂度与空间复杂度分析。
3.能够运用数据结构的基本原理和方法进行问题的分析与求解,具备采用 C 或 C++ 语言设计与实现算法的能力。
【考纲内容】
1.数据结构的基本概念;
2.算法的基本概念。
【考情统计】
年份
题数及分值
考点
单选题
综合题
总分值
2009
0
0
0
未直接考察本章内容
2010
0
1
2
时间复杂度、空间复杂度
2011
1
1
4
时间复杂度、空间复杂度
2012
2
1
5
时间复杂度
2013
1
1
4
时间复杂度、空间复杂度
2014
1
0
2
时间复杂度
2015
0
1
2
时间复杂度、空间复杂度
2016
0
1
2
时间复杂度、空间复杂度
2017
1
0
2
时间复杂度
2018
0
1
2
时间复杂度、空间复杂度
2019
1
1
4
时间复杂度
2020
0
1
2
时间复杂度、空间复杂度
2021
0
1
2
时间复杂度、空间复杂度
2022
1
1
4
时间复杂度、空间复杂度
2023
1
0
2
时间复杂度、空间复杂度
2024
0
0
0
未直接考察本章内容
【考点解读】
在 408 考试中,本章内容几乎每年都有相关考查,主要是考查时间复杂度和空间复杂度的计算。通常是考一个时间复杂度分析的选择题(占 2 分)。有些年份在算法题的最后一问中,也会要求分析所写算法的时间和空间复杂度(占 2 - 3 分)。本章常见的题目形式有:给定一段循环或递归代码,分析时间复杂度;分析算法代码的空间复杂度等。
【复习建议】
本章内容虽然考试所占分值较少,但时间和空间复杂度的分析是每个 408 考生都必须要掌握的基本内容。重点掌握时间复杂度和空间复杂度的分析方法。而对于数据结构的基本概念,了解即可。另外,第一次学习这些概念不理解也没关系,学完本书再回过头来看这些概念,会有更进一步的理解。
2.1 数据结构的定义
数据结构暂时没有一个统一的官方定义。这里仅列举一个容易理解的定义:数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。
数据结构是一门讨论 “描述现实世界实体的数学模型及其之上的运算在计算机中如何表示和实现” 的学科。数据结构包含以下三个方面的内容:
(1)数据的逻辑结构:指从具体问题中抽象出来的、描述数据之间逻辑关系的、与数据的存储无关的数学模型。本书后面介绍的线性表、树、图均是指逻辑结构。
(2)数据的物理结构:指数据的逻辑结构在计算机中的存储实现 ²。常用的物理结构如下:
①顺序存储结构:利用数据元素在存储器中的相对位置来表示元素之间的逻辑关系 ³,通常用数组来实现。
②链式存储结构:是一种数据元素的逻辑地址相邻、物理地址不一定相邻的存储结构,通过链表中指针的链接次序来表示数据元素之间的逻辑顺序。
提示:数据元素是数据的基本单位,用于完整地描述一个对象 ²。数据元素通常也称为结点、顶点、记录、元素等。
③散列存储结构:根据元素的关键字通过散列函数计算出一个值,并将这个值作为该元素的地址 ²。散列存储结构在本书 7.5 小节(第 333 页)有详细介绍。
④索引存储结构:索引通常基于一个或多个字段的值创建,这些字段被称为索引键。索引通过将索引键与对应的数据记录或数据块建立关联,形成索引项。索引项可以按照特定的排序顺序组织,以支持快速的查找和范围查询操作。
(3)数据的运算:指对数据实施的操作。常见的运算有:对数据的增删改查、排序等。
数据的逻辑结构和物理结构的区别:物理结构是数据在计算机内存中的存储方式,比如顺序存储、链式存储、散列存储等,逻辑结构是描述数据与数据之间的关系,比如队列、栈、堆等。
2.2 算法的基本概念
算法(Algorithm)还没有一个公认的定义,这里列举一个易于理解的定义:算法是为了解决某类问题而规定的一个有限长的操作序列。
一个算法必须具备以下 5 个要素:
1.有穷性:在执行有限次基本操作之后结束。
2.确定性:在算法中明确规定了每种情况应该执行的操作。相同的输入得到相同的输出,不存在二义性。
3.可行性:算法由若干语义明确的基本操作组成。算法中所有的操作均可通过调用有限次基本操作来实现。
4.输入:可理解为对所求的特定问题的描述。一个算法可以有零个或多个输入。
5.输出:可理解为输入的问题经过算法的处理之后,得到的求解结果。算法至少有一个输出。
提示:算法和程序的区别在于,算法主要描述解决问题的方法,而程序则是通过某种计算机语言具体实现一个算法。
一个算法的好坏可以从以下几个方面来评价:
1.正确性:指算法执行后能得到预期的结果。这是算法最基础的功能。
2.易读性:算法应方便使用,便于理解、修改、调试。
3.健壮性:具备对异常情况的处理。
4.高效性:执行效率高,合理占用存储空间。
2.3 算法效率分析
衡量算法效率的指标主要有两个:时间复杂度和空间复杂度。
2.3.1 时间复杂度
1.时间复杂度的定义
①问题规模 n 指算法的输入量。可以理解为数据量的大小。例如:在排序算法中,问题规模 n 指参与排序的数据个数;在树相关运算中,问题规模 n 指树中结点的个数。
②语句频度指一条语句的重复执行次数 ³。
③基本语句指对算法运行时间影响最大的语句。
④用函数 f (n) 表示一个算法中基本语句的频度之和。
时间复杂度(Time complexity),常用函数 T (n) 表示,指基本语句执行次数的数量级。通常用符号 O(Order 的简写)表示取数量级的操作,则有:
T (n)=O (f (n))
提示:一个算法中所有语句频度之和与基本语句频度之和是同一个数量级的。
2.时间复杂度计算
时间复杂度的计算步骤为:
(1) 找到所有语句中执行次数最多的那条语句作为基本语句。
(2) 计算基本语句执行次数的数量级。
(3) 取其数量级用大 O 来表示即可。
例如,有以下 A、B、C 三段代码:A: A[0][0]++;B:for (int i = 0; i < n; i++)①A[i][i] += 2;②C:for (int k = 0; k < n; k++)①if (A[k][k] > 9)②for (int i = 0; i < n; i++)③for (int j = 0; j < n; j++)④++A[i][j];⑤
(1) 在代码 A 中:基本语句 “A [0][0]++” 的执行次数为 1 次,算法的时间复杂度 T (n)=O (1)。
(2) 在代码 B 中:基本语句 “A [i][i]+=2;” 的执行次数为 n 次,算法的时间复杂度 T (n)=O (n)。
(3) 在代码 C 中:基本语句 “++A [i][j]” 的执行与判断语句 “if (A [k][k]>9)” 有关,若该判断语句一直不成立,则基本语句 “++A [i][j]” 的执行次数为 0。反之若一直成立的话,则执行次数为 n³,算法的时间复杂度 T (n)=O (n³)。
3.时间复杂度的分类
算法的时间复杂度不仅与问题规模 n 有关,还和其他因素有关,比如:初始状态等。
(1) 在算法计算量最小的情况的复杂度称为最好时间复杂度。例如:在上述的程序 C 中,若语句②一直不成立,则该情况下的时间复杂度称为该算法的最好时间复杂度。
(2) 在算法计算量最大的情况的复杂度称为最坏时间复杂度。例如:在上述的程序 C 中,若语句②一直成立,则该情况下的时间复杂度称为该算法的最坏时间复杂度。
(3) 在所有可能情况下,各种输入情况等概率出现,算法的计算量取加权平均值,在该情况下的复杂度称为平均时间复杂度。例如:在上述的程序 C 中,若语句②成立的概率为 1/n,则该情况下的时间复杂度称为该算法的平均时间复杂度。
通常,时间复杂度默认指最坏时间复杂度。
4.常用时间复杂度大小的比较
O (1)<O (log₂n)<O (n)<O (nlog₂n)<O (n²)<O (n³)<O (2ⁿ)<O (n!)<O (nⁿ)
常数阶,对数阶,线性阶,线性对数阶,平方阶,立方阶,指数阶,阶乘阶,n次方阶
提示:任意底数的对数复杂度都是同阶的,例如:O (log₂n)=O (log₃n)。
5.时间复杂度的相关总结
(1) 递归算法的时间复杂度计算方法比较固定,这里举例说明:
假设有如下递归公式,计算 T (n) 的时间复杂度。
观察 T (n) 的递归函数,可以将 T (n) 递归成
T (n)=T (n - 1)+1=T (n - 2)+2=T (n - 3)+3=…=T (1)+n - 1
可以看出,该递归的时间复杂度为 O (n)。
提示:还可以用算法分析中的主定理(master theorem)来快速求解递归式算法的时间复杂度,感兴趣的考生可以自行了解相关知识。(2) 基本操作:只有常数项,时间复杂度为 O (1)。
(3) 顺序结构:时间复杂度按加法规则进行相加。
(4) 分支结构:时间复杂度按最坏情况下的时间复杂度进行计算。
(5) 循环结构:时间复杂度按乘法规则进行计算。
(6) 分析具体算法的时间复杂度时,一般分析最坏情况下的时间复杂度。
(7) 利用时间复杂度判断算法的效率高低时,只需关注基本操作的最高次项即可。
例如:O (n² + 2ⁿ),则认为该算法的时间复杂度为指数阶。
(8) 加法规则:T (n)=T₁(n)+T₂(n)=O (h (n))+O (f (n))=O (max (h (n),f (n)))。
(9) 乘法规则:T (n)=T₁(n)×T₂(n)=O (h (n))×O (f (n))=O (h (n)×f (n))。
2.3.2 空间复杂度
一个具体的算法在占用的存储空间主要包括三部分:
(1)存储程序本身的空间:比如具体的程序代码。
(2)存储算法输入和输出数据的空间:与问题规模 n 有关。
(3)对数据进行辅助操作的临时变量所占存储空间:分析算法的空间复杂度,就是分析这部分的空间。
空间复杂度(Space complexity)是对一个算法在运行过程中临时占用存储空间的大小的量度 ²。若问题的规模为 n,f (n) 是算法运行过程中需要的辅助存储空间的函数,符号 O(Order 的简写)表示取数量级的操作。用 S (n) 表示空间复杂度,则:
S (n)=O (f (n))
若算法在运行过程中占用的临时空间为常数级别(即:O (1)),则称算法是原地工作的。
递归算法的空间复杂度 = 每次递归的空间复杂度 × 递归深度。要求递归的深度是因为每次递归所需空间都被压到调用栈,所以该栈最大的长度就是递归的深度。一般每次递归中需要的空间是一个常量,每次递归的空间复杂度皆为 O (1)。
2.4 章末总结
(1)理解 “逻辑结构” 和 “物理结构” 的概念,理解它们两者之间定义的区别。
(2)重点掌握根据具体代码分析算法的时间和空间复杂度的方法,牢记时间复杂度的 8 条计算规则。
2.5 习题精编
1.某算法的时间复杂度为 O (n²),表明该算法的( )。
A. 问题规模是 n²
B. 执行时间等于 n²
C. 执行时间与 n² 成正比
D. 问题规模与 n² 成正比1.【参考答案】 C
【解析】 假设问题规模为 n,且时间复杂度T(n)=kn^2(k 为常数系数),则可以得到时间复杂度T(n)=O(n^2)。可以看出 A、B 和 D 不正确,因为此时规模为 n,执行时间为kn^2。2.下面所示的各个数据结构中,属于非线性数据结构的是( )。
A. 树结构
B. 队列结构
C. 栈结构
D. A、B、C 均是2.【参考答案】 A
【解析】 树是一种分支结构,属于非线性数据结构。栈和队列属于线性数据结构。3.下面所示的各个选项中,( )属于逻辑结构。
A. 顺序表
B. 散列表
C. 有序表
D. 单链表3.【参考答案】 C
【解析】 有序表是一种逻辑结构,“有序” 指明表中的数据是按一定的逻辑顺序进行排列的,属于逻辑结构。顺序表、散列表和单链表都是物理结构。4.以下算法的时间复杂度为( )。
void test(int n) {int i = -520;for (i = 0; i*i*i <= n; )++i; }
A. O(n)
B. O(nlogn)
C. O(n¹/³)
D. O(n¹/²)4.【参考答案】 C
【解析】 观察程序发现,语句 “++i;” 执行频率较高,且该语句影响 for 循环判断语句。
(1) 假设该语句执行了 t 次,当执行到 t + 1 次时,for 的判断语句为i∗i∗i>n。
(2) 由于 i 在循环里的初始值等于 0,当语句 “++i” 执行到第 t 次结束时,i=t。结合 (1) 的结论,有O(t^3>n),解得t>n^(1/3),即有T(n)=O(n^(1/3))。选择 C。5.以下代码在最坏情况下的时间复杂度为( )。
for (k = n - 1; k >= 1; --k)for (t = 1; t < k; ++t)if (A[t] > A[t + 1])swap(A[t],A[t + 1]); // 将A[t]和A[t + 1]对换
A. O(n)
B. O(nlogn)
C. O(n³)
D. O(n²)5.【参考答案】 D
【解析】 观察程序发现,语句 “A[j]与A[j+1]对换;” 执行频率较高,且该语句并不影响双重 for 循环判断语句,但会影响 if 的判断语句。当 A 数组的所有元素都逆序时,语句 “A[j]与A[j+1]对换;” 会一直执行,此时为最坏情况。该语句在最坏情况下的频度是O(n^2),选 D。6.下面算法中,语句 “x *= 2;” 的执行次数是( )。
int x = 1; for (int i = 0; i < n; ++i)for (int j = i; j < n; ++j)x *= 2;
A. n(n + 1)/2
B. nlog₂n
C. n²
D. n(n - 1)/26.【参考答案】 A
【解析】 经过观察发现,语句 “x∗=2;” 不影响双重 for 循环的判断条件,且内部 for 循环受外部 for 循环约束,每轮内循环执行的次数为(n−i)次,外循环共执行 n 次。因此 “x∗=2;” 共执行t=n+(n−1)+(n−2)+⋯+(n−(n−1))=(n+1)n/2,选择 A。7.若下列代码中 combine 函数的时间复杂度为 O (n)。则 combineSort 函数的时间复杂度是( )。
void combineSort(int i, int j) {if (i != j) {int k = (i + j) / 2;combineSort(i, k);combineSort(k + 1, j);combine(i, j, k); // 本函数的时间复杂度为O(n)} }
A. O(n²)
B. O(n³)
C. O(n³/²)
D. O(nlog₂n)7.【参考答案】 D
【解析】 该程序代码是归并排序的核心代码段。(具体归并排序的操作可翻看本参考书相关章节的讲解)假设 n 个元素进行 2 路归并,设 s 为归并趟数,则2^s≥n,可推导出归并趟数s=⌈log2n⌉。由于 n 个元素进行 2 路归并需要进行⌈log2n⌉趟归并操作,每一趟都需对 n 个数据元素进行处理,因此 2 路归并排序的时间复杂度为O(nlog2n)。8.下列各个选项的说法中,表述有错误的是( )。
I. 算法原地工作的含义是指不需要任何额外的辅助空间
II. 在相同规模 n 下,当 n 足够大时,复杂度为 O (n) 的算法时间上总优于 O (2ⁿ) 的算法
III. 所谓时间复杂度是指最坏情况下估算算法执行时间的一个上界
IV. 同一个算法,实现语言的级别越高,执行效率越低
A. I
B. I,II
C. I,IV
D. III8.【参考答案】 A
【解析】I 错误,算法原地工作的含义是指需要常量级别的辅助空间,而不是不需要任何额外辅助空间,这两者有本质区别。II 正确,时间复杂度指的是渐进时间复杂度。在 n 足够大的情况下,根据记号 O 的定义可以得出,时间复杂度为 O (n) 的算法一定更优于 O (2ⁿ) 的算法。
III 正确,分析时间复杂度是总是考虑该算法在最坏情况下的时间复杂度,这是为了保证算法的运行时间不会比这更长,即最多需要运行多少时间。
IV 正确,该句是严蔚敏老师教材中的原话。一般情况下对同一个算法而言,实现语言的级别越高,其执行效率会越低。可以理解成,高级别的语言一般是由许多低级别语言 “组成实现” 的,虽然这样使得高级别语言的 “包装” 更为完整、使用更加简单便捷,但一般需要付出比低级别语言更多的执行和维护花销。
9.计算机算法指的是(1),它必须具备(2)这三个特性。
(1) A. 计算方法
B. 排序方法
C. 解决问题的步骤序列
D. 调度方法
(2) A. 可执行性、可移植性、可扩充性
B. 可执行性、确定性、有穷性
C. 确定性、有穷性、稳定性
D. 易读性、稳定性、安全性9.【参考答案】 (1) C (2) B
【解析】 算法:是解决具体问题的步骤方法,是一系列操作指令的有穷序列。通常每条指令中可以包含有一或多个小的操作。一般具有五大特性:有穷性、确定性、可行性、输入和输出。10.下列关于数据结构的说法中错误的是( )。
A. 数据结构相同,对应的存储结构也相同
B. 数据结构涉及数据的逻辑结构、存储结构和施加在其上的操作
C. 数据结构操作的实现与存储结构有关
D. 定义逻辑结构时可以不考虑存储结构10.【参考答案】 A
【解析】 A 错误,相同的数据结构也可以使用不同的存储结构存储。例如:线性表既可以使用顺序存储,也可以使用链式存储。其它选项说法正确。对于 C 选项,一般而言,若存储结构不同,相应的基本操作也会有区别。11.下列说法中,不正确的是( )。
A. 数据元素是数据的基本单位
B. 数据项是数据元素中不可分割的最小可标识单位
C. 数据可由若干数据元素构成
D. 数据项可由若干个数据元素构成11.【参考答案】 D
【解析】 数据元素:是数据的基本单位。在计算机中一般将数据元素作为一个整体来考虑和计算。一封信可作为一个数据元素,而信中包含的 “邮编”、“送信人” 等信息为该数据元素的数据项。即若干个数据项可组成一个数据元素。D 选项说法反了。12.数据的四种基本存储结构是指( )。
A. 顺序存储结构、索引存储结构、直接存储结构、倒排存储结构
B. 顺序存储结构、索引存储结构、链式存储结构、散列存储结构
C. 顺序存储结构、非顺序存储结构、指针存储结构、树型存储结构
D. 顺序存储结构、链式存储结构、树型存储结构、图型存储结构12.【参考答案】 B
【解析】 数据的基本存储结构有 4 种:顺序存储结构、索引存储结构、链式存储结构、散列存储结构。具体概念可参照辅导书相关章节。考生同时需注意存储结构和逻辑结构的区别。13.下面关于 “算法” 的描述,错误的是( )。
A. 算法必须是正确的
B. 算法必须要能够结束
C. 一个问题可以有多种算法解决
D. 算法的某些步骤可以有二义性13.【参考答案】 D
【解析】 根据算法的五大特性之一的 “确定性” 可知,算法 (1) 每一条指令的操作都是确定的,理解上是没有二义性的。(2) 对于同一套输入和相同的执行环境,必须产生同一套的执行输出结果。D 选项错误。14.某算法的空间复杂度为 O (1),则( )。
A. 该算法执行不需要任何辅助空间
B. 该算法执行所需辅助空间大小与问题规模 n 无关
C. 该算法执行不需要任何空间
D. 该算法执行所需空间大小与问题规模 n 无关14.【参考答案】 B
【解析】 算法的空间复杂度为 O (1) 表示的是执行该算法所需的辅助空间大小相较于数据量来说是个常量,而不代表不需要任何空间或辅助空间,所以 A,C 错误。执行算法所需空间大小与问题规模有关(例如一般而言数据量的大小,会影响算法执行时存储这些数据的所需空间大小),该算法的辅助空间大小与问题规模无关,D 错误。算法的辅助空间不随着问题规模的变化而变化。选择 B。15.一个算法所需要时间由下述递归方程表示,试求出该算法的时间复杂度。
其中:n 是问题规模,为简单起见,设 n 是 2 的整数幂。
16.将下列函数,按它们在 n 到无穷时的无穷大阶数,从小到大排列。
17.斐波那契数列Fn定义如下:
F0=0, F1=1, Fn=Fn−1+Fn−2, n=2,3,⋯
请就此斐波那契数列回答下列问题。
(1) 在递归计算Fn的时候,需要对较小的Fn−1, Fn−2, ⋯, F1, F0精确计算多少次?
(2) 如果用大 O 表示法,试给出递归计算Fn时递归函数的时间复杂度是多少。
2.6 真题演练
18.【2011】设 n 是描述问题规模的非负整数,下面程序片段的时间复杂度是( )。
x = 2;while (x < n/2)x = 2 * x;
A. O(log₂n)
B. O(n)
C. O(nlog₂n)
D. O(n²)
19.【2012】求整数 n(n≥0)阶乘的算法如下,其时间复杂度是( )。
int fact(int n) {if (n <= 1) return 1;return n * fact(n - 1); }
A. O(log₂n)
B. O(n)
C. O(nlog₂n)
D. O(n²)
20.【2014】下列程序段的时间复杂度是( )。
count = 0; for (k = 1; k <= n; k *= 2)for (j = 1; j <= n; j++)count++;
A. O(log₂n)
B. O(n)
C. O(nlog₂n)
D. O(n²)
21.【2017】下列函数的时间复杂度是( )。
int func(int n) {int i = 0, sum = 0;while (sum < n) sum += ++i;return i; }
A. O(log₂n)
B. O(n¹/²)
C. O(n)
D. O(nlog₂n)
22.【2019】设 n 是描述问题规模的非负整数,下列程序段的时间复杂度是( )。
x = 0; while (n >= (x + 1) * (x + 1))x = x + 1;
A. O(log₂n)
B. O(n¹/²)
C. O(n)
D. O(n²)