提到王道《数据结构》复习书,第一章往往是大多数考研人翻开后最先放弃的一章:定义绕口,内容抽象,好像全是“背诵任务”。我第一轮复习时也是这样,翻了两页就跳到第二章线性表,结果到做综合题的时候才意识到,第一章根本不是用来背的,而是用来定位整本书坐标系的。这篇内容想解决的问题很具体:第一章到底讲了几条线,每条线上的概念应该怎么理解,以及复杂度分析怎么算又快又稳。无论你在用王道单科书、严蔚敏老师的经典教材,还是配套王卓老师的网课视频,下面的梳理方式都适用。适合刚开始第一轮的零基础/跨考选手,也适合已经看到树、图再回头补漏洞的同学。
1. 第一章的真实坐标:不是背诵章,而是整本书的“地质分层”
1.1 真题账面:直接考没有几分,间接考到处都是
很多人对第一章的轻视来自于“感觉”:翻翻历年题,好像没有大题目会单独出一道“什么是数据结构”,觉得它是纯概念章。这个判断部分正确,但危险就藏在“部分”两个字里。
以统考408风格为例,第一章直接对应的题目通常只出现在选择题,常见形式是:给你一个实际场景,让你判断逻辑结构;或者给出几个复杂度,让你比较大小;再或者把四个存储结构放在一起,问哪个说法正确。这种题分值不大,往往就一两道选择。但如果把视角放大到整张试卷,你会发现后面几乎每一道算法题都在考察第一章的两个能力:第一,能说清楚自己对数据采用了什么存储结构;第二,能对写出来的算法进行复杂度分析。第二点尤为致命,很多同学树和图的代码写出来了,最后却在“该算法时间复杂度是多少”上失分,根子就是在第一章只背了大O符号,没有真正理解“怎么数执行次数”。
自命题院校里,第一章这种概念章反而更容易被出成判断题或填空题,比如“数据元素是数据的最小单位”对不对、“算法可以没有输入”对不对。这类题目不考计算,考的是概念边界,而概念边界恰恰是大多数人懒得细抠的地方。所以我的建议是:第一轮复习时,把第一章当作一个“倒逼自己建立概念边界”的机会,而不是当作需要快速跳过的前菜。
1.2 一张内容地图帮你把第一章的线理清楚
王道第一章看上去散,但只要把线抽出来,就三个主线:概念线、结构线、算法线。
概念线是从“数据”这种最宽泛的符号集合出发,逐步精确定位到“数据元素”“数据项”“数据对象”,最后给出“数据结构”这个核心定义。这条线回答的是:我们在讨论什么样的对象?这里的难点在于,这些词在中文语境里边界模糊,考试却要求你给出准确判断。
结构线解决的是更核心的问题:拿到一批数据之后,怎么描述它们之间的逻辑关系,怎么在计算机里真正落地。逻辑关系有四种典型:集合、线性、树形、图状;落地也就是物理存储有四种主流手段:顺序存储、链式存储、索引存储、散列存储。复习时要时刻记住,逻辑结构描述的是数据之间的抽象关系,存储结构描述的是这种关系在内存里的实现方式,两者不是一一对应的。
算法线相对独立,前半部分是算法的定义和五个特性,后半部分是时间复杂度与空间复杂度的计算。这条线不是为第一章服务的,而是为整本书所有算法服务的。每当你学一个新结构,后续都会有插入、删除、查找等操作,这些操作的优劣都要靠复杂度来评价。
这三条线不是并列关系,而是层层递进:先明确数据对象,再讨论逻辑和存储,最后用算法去操作它。把这三条线印在脑子里,再看王道第一章的目录,就不会觉得这一段一段只是名词堆砌了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 概念阶梯:从“数据”到“数据项”,别在层级的名字上翻车
2.1 用“学生信息管理”把四级概念一次串熟
我在带别人复习时发现,单纯背“数据元素是数据的基本单位”这句话,过两天一定会忘。更好的做法是拿一个具体例子从头串一遍。
假设你要给全校学生做一个信息管理系统,那么:
- 数据:所有能被计算机处理的符号总称,比如学生姓名、学号、性别、成绩,以及这些符号形成的一切记录。它是最大的集合概念。
- 数据元素:数据的基本单位,通常作为一个整体被处理。比如一条完整的学生记录,包含学号、姓名、性别、成绩,当你执行“添加一名学生”操作时,添加的这条记录就是一个数据元素。
- 数据项:构成数据元素的不可分割的最小单位。比如一条学生记录里的“学号”字段、一个“姓名”字段。注意,数据项讨论的是构成关系,一个数据元素可以包含一个或多个数据项。
- 数据对象:性质相同的数据元素的集合,是数据的子集。比如“全体学生的记录”就是一个数据对象,因为在同一个集合里,每个元素的字段构成都相同。
我用一个表格把这几个概念的关系摆出来,后面做题时直接对照:
| 概念 | 核心定义 | 一句话示例 |
|---|---|---|
| 数据 | 客观事物的符号表示,能被计算机处理 | 所有学生信息的总和 |
| 数据元素 | 数据的基本单位,整体参与操作 | 一名学生的完整记录 |
| 数据项 | 构成数据元素的最小单位,不可分割 | 学生记录中的“学号”字段 |
| 数据对象 | 性质相同的数据元素的集合 | 全部学生记录组成的集合 |
判断题最爱在这里设陷阱。最典型的一种错误说法是“数据元素是数据的最小单位”,正确表述应该是“数据项是数据的最小单位”。还有一个容易忽略的点:数据对象强调的是“性质相同”,所以不能说“全校的老师加学生组成一个数据对象”,因为教师记录和学生记录字段不同,性质不同,它们属于另一个集合。
2.2 “带结构的数据元素的集合”重点在“结构”而不是“集合”
王道在第一章给出的经典定义是:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。很多人背的时候把重心落在“集合”上,觉得数据结构就是一堆元素堆在一起,这是理解上的硬伤。定义里的关键修饰词是“相互之间存在一种或多种特定关系”。
举个例子,几十个人站在操场上,如果没有规定任何关系,这只是一个集合;但如果他们按照学号排队,前后顺序就构成了一对一的线性关系;如果他们按班级、专业组成建制,就构成了层次关系;如果他们通过社交软件互相添加好友,就是更复杂的多对多关系。同样一批数据对象,附加的关系不同,得到的数据结构是完全不同的。
这里可以引出另一个高频考点:数据结构包含三个方面,分别是逻辑结构、存储结构和数据的运算。这三者不是三个独立概念,而是同一个东西的不同视角。逻辑结构描述数据元素之间的抽象关系;存储结构决定这种关系在内存里如何呈现;数据的运算则是在给定逻辑结构和存储结构的前提下能对数据做哪些操作。学习第二章线性表时,你会体会到“逻辑结构相同、存储结构不同”会导致同样操作写出完全不同风格的代码,原因就在这里。
做题时,我建议大家看到一个定义题,先划出主语和修饰语。比如“数据结构是带结构的数据元素集合”,主语是集合,修饰语是带结构。如果题目里说“数据结构就是数据元素的集合,不含运算”,这种说法就是错的,因为它漏掉了逻辑、存储和运算三个维度中的运算部分。
2.3 ADT:第一章埋伏好的抽象思想
王道在这一章会提到抽象数据类型,英文缩写ADT。很多第一轮复习的同学觉得这部分可有可无,因为看起来像是一种“格式”。但实际上,ADT是理解后续栈、队列、串、树实现的重要思想。
一个ADT通常包含三个部分:数据对象、数据关系、基本操作。所谓抽象,就是只关心“能做什么”,不关心“怎么做”。比如你要用栈,只要知道它有入栈、出栈操作,以及它是后进先出的,就可以在更高层的问题里使用它。至于底层是用数组实现还是用链表实现,是栈这个逻辑结构的选择,不属于使用者的关注范围。
这种“接口与实现分离”的思想,在后续章节里会反复出现。第一次学线性表时,你会看到两个平行的章节:一个叫顺序表,一个叫链表。它们逻辑上都是线性表,对外都支持插入删除查找,但因为存储结构不同,代码差异非常大。如果你在第一章就理解了ADT的意义,就不会把“顺序表”和“链表”当作两个完全不相干的数据结构,而会把它们看成“同一个逻辑结构的两套存储实现”。
判断“什么属于ADT定义”也是自命题喜欢考的。凡是具体描述怎么做、用什么语言实现、用数组还是指针之类的内容,都不属于ADT的定义,只属于实现细节。做题时只要看到讨论实现细节的选项,基本可以直接排除。
3. 逻辑结构的四种分类:能对实例做映射才算学会
3.1 四大家族的核心判断标准
王道教材把逻辑结构分成集合、线性、树形、图状四类。理解它们的最短路径不是背名字,而是抓住“数据元素之间到底是什么关系”。
- 集合结构:数据元素之间只有“属于同一个集合”的关系,互相之间没有其他逻辑关系。这里最常被误解,因为“同一个集合”也是一种关系,但数据结构里讨论的关系更多指前后、层次、网络这类有结构的关系。所以当题目问“一堆学生排队”时,一定不是集合结构,因为排队有前后顺序。
- 线性结构:数据元素之间存在一对一的关系。最直观的就是排队:除了第一个元素没有前驱、最后一个元素没有后继之外,每个元素都有唯一的前驱和唯一的后继。数组、链表、栈、队列在逻辑上都是线性结构。
- 树形结构:数据元素之间存在一对多的层次关系。典型例子是文件目录:一个文件夹下面可以挂多个子文件夹,但每个子文件夹只有一个父文件夹。当然真实文件系统会涉及快捷方式之类的特例,考试中除非特别说明,都按标准树形处理。
- 图状结构:数据元素之间存在多对多的关系。城市之间的交通网络就是典型,一个城市可以与多个城市直接相连,逻辑关系没有任何层次约束。
还有一个更上位的分类方式,把线性结构和树形结构、图状结构区分开:所有元素都处在一个“序列”里就是线性,否则就是非线性。因此“数据结构可以分为线性结构和非线性结构两大类”这种说法也是正确的。选择题里经常会把这两个分类体系混在一起出选项,比如“数据结构分为集合、线性、树形和图状,其中线性属于非线性结构”,后半句就是错的。
3.2 真正的难点:从具体场景反推结构类型
考试不是直接问“线性结构定义是什么”,而是给你一个场景,问你它属于什么逻辑结构。这种题特别考察你是否真的理解了关系。
我看到很多同学面对“图书馆的书架”会犹豫:书架上的书一行一行整齐排列,看起来像线性结构。这里的判断关键不在“物理上排得整齐”,而在于题目强调的关系。如果题目说“图书按编号顺序排列,每本书有唯一的前驱和后继”,那就是线性结构。如果题目讨论的是“图书分类目录”,一个类别下有很多子类,像树一样展开,那应该是树形结构。同一个对象,在不同视角下可以映射到不同结构,要做的是优先捕捉题目给出的关系线索,而不是凭生活经验想当然。
再举几个常见的例子:操作系统中多级目录的文件系统是树形结构;地铁线路图是图状结构,因为多个站点之间可以形成环;CPU执行函数调用时的调用栈是线性结构,后调用的函数先返回;一个班级的同学按学号排好队,每个同学有一个直接前驱和一个直接后继,是线性结构。
建议第一轮复习时,自己拿笔记本列两列。左边写场景,右边写判断依据和结构类型。不要写太多,把王道书上出现的例题场景都列一遍就足够,重点是逼自己说出“为什么”。
3.3 逻辑结构和存储结构不要绑定记忆
逻辑结构讨论的是抽象关系,存储结构讨论的是物理实现,这一对概念经常被混在一起。最典型的错误思维是:线性表一定用顺序存储,树一定用链式存储,图一定用矩阵。真实情况完全不是这样。
同样的线性结构,既可以用数组实现为顺序表,也可以用节点加指针实现为链表;同样的树形结构,既可以用孩子兄弟表示法实现,也可以用数组形式实现,比如堆排序里就用数组存储完全二叉树;同样的图,既可以用邻接矩阵,也可以用邻接表,前者本质上是顺序存储思想,后者就是链式存储思想。
为什么这个区分如此重要?因为在后续章节里,不同存储结构会直接影响算法的时间复杂度。比如在一个顺序表里,按下标取第i个元素的时间复杂度是O(1),因为地址可以通过起始地址加偏移量算出来;但在链表里,就算你知道要取第i个元素,也得从头一个个找过去,时间复杂度是O(n)。同样是“取第i个元素”这个逻辑操作,在两个不同的存储实现下表现完全不同。第一章如果建立了“逻辑结构不决定存储结构”的意识,后期学线性表时会顺利很多。
4. 四种存储结构的选择本质是一笔“性能账”
4.1 顺序存储、链式存储、索引存储、散列存储各自的性格
存储结构解决的核心问题是:数据之间的逻辑关系,在内存里靠什么机制维持。四种主流方案各有各的维持方式。
顺序存储的核心特点是逻辑相邻即物理相邻。用数组存放数据,下标i和i+1的元素在内存地址上也相邻。这种方案的优点是:只要知道起始地址和每个元素大小,就能通过公式直接算出第i个元素的地址,属于随机存取。缺点是插入和删除操作往往需要移动大量元素来保持相邻关系,且申请内存时需要一段连续空间。这就像电影院里一排固定座位,你要坐第10座,走过去就行,但如果有人要插队坐在你和你邻座中间,后面的人全都得挪。
链式存储的核心特点是不要求物理相邻,元素之间靠指针建立联系。每个节点除了存储数据,还存储指向下一个节点的指针。插入和删除操作只需要修改指针,无需移动数据本身,这是它最大的优势。但代价是每个节点都要额外占用指针空间,并且因为不能通过公式算出某个节点的地址,无法做到随机存取,查找某个指定位置的节点只能从头遍历。这就像一群人站成一排,每个人只拉着前一个人的衣角,你想找排在第50位的人,只能从队首开始数。
索引存储通过在数据元素之外建立一张索引表,用关键字和地址的对应关系来加速查找。图书馆的馆藏目录就是这样:书还是按序摆放在书库里,但你不用去书库里挨本找,先查目录,拿到书的位置编码再进去取。这种结构适合数据量大且需要频繁按关键字检索的场景,缺点是多维护一张索引表,额外空间开销大,更新索引也需要时间。
散列存储的思路更直接:根据元素的关键字,通过一个散列函数直接计算出存储地址。理想情况下,想要查找某个记录,不用比较,直接算地址就能取。散列的问题是所谓冲突:两个不同的关键字可能算出同一个地址。解决冲突需要额外处理,这会消耗时间。四种方式里,散列最考验设计功力,选择什么样的散列函数、散列表长度设置多大、冲突了怎么处理,都会直接影响性能表现。
4.2 从C语言的视角加深“存储机制”的印象
只看文字描述还是太虚,建议至少动手写一段C代码来感受顺序和链式的区别。
顺序存储最朴素的样子就是:
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int length;
} SeqList;
两个相邻元素之间的地址关系是连续的,取第i个元素直接 data[i - 1],凭下标一步到位。
链式存储的节点定义则长这样:
c复制typedef struct LNode {
int data;
struct LNode *next;
} LNode;
每个节点里除了数据还有一个指针域,指向下一个节点。你能从当前节点跳到下一个节点,但没法凭空算出第i个节点在哪里。
我建议基础薄弱的同学在第一轮复习时,亲手把这两个结构定义敲一遍,再写一个通过下标取元素的小函数对比跑一下。代码本身不难,难的是通过代码理解“随机存取到底是什么意思”。一旦你意识到链表取第i个元素必须循环遍历,后续学查找和排序时很多困惑会提前消失。
4.3 一张对比表,把选择题高频选项一网打尽
| 存储结构 | 逻辑相邻与物理相邻关系 | 是否支持随机存取 | 额外空间 | 插入删除操作特点 | 典型应用 |
|---|---|---|---|---|---|
| 顺序存储 | 物理相邻 | 支持 | 基本无 | 可能需要移动大量元素 | 数组、顺序表 |
| 链式存储 | 物理不一定相邻,靠指针维持 | 不支持 | 每个节点多一个指针域 | 只改指针,不移动数据 | 链表 |
| 索引存储 | 数据区可以任意,另建索引表 | 借助索引后可快速定位 | 索引表额外开销 | 需要同步维护索引 | 数据库索引思想 |
| 散列存储 | 与关键字相关,无固定相邻关系 | 通过散列函数直接定位 | 哈希表本身及冲突处理 | 冲突时性能波动 | 哈希表、缓存系统 |
这张表里的“随机存取”四个字是选择题的钉子户。判断题最爱说“链式存储也能随机存取,因为可以通过指针跳转”,这种说法是错的。通过指针跳转本质上还是顺序访问,你访问第n个节点前必须先知道前n-1个节点在哪里,这就是为什么链表的按位置查找时间复杂度是O(n)。
另一个高频考点是存储密度。顺序存储不需要额外指针,存储密度高,通常认为是1;链式存储每个节点都包含指针域,存储密度恒小于1。但注意,这不代表链式存储一定比顺序存储省空间。因为顺序存储通常要一次性申请足够大的连续空间,如果实际数据量远小于申请量,浪费的空间可能比链表的指针开销还大,所以比较空间开销必须放在具体场景下讨论。
5. 复杂度计算的完整思路:不要硬背公式,要学“数次数”
5.1 为什么懂大O比会算精确次数更重要
时间复杂度描述的不是某段代码跑了多少毫秒,而是随着问题规模n不断增大,基本语句执行次数的增长趋势。王道教材里强调“基本运算的执行次数”,因为程序实际运行时间受硬件、语言、编译器影响太大,不能作为衡量算法优劣的标准,但“执行了多少次基本操作”是可以脱离机器分析的。
大O记号的核心思想是忽略常数和低阶项,只看增长最快的部分。比如执行次数 T(n) = 3n^2 + 2n + 100,当n变得非常大时,2n和100这两部分相对于3n^2来说几乎可以忽略,增长趋势由n^2决定,所以记作O(n^2)。再从代码本身说,循环嵌套的次数决定了基本操作执行的次数,这也解释了为什么第一章要先讲清楚序列和循环,因为复杂度计算本质上就是数循环嵌套层数。
做题时需要区分最坏时间复杂度、平均时间复杂度和最好时间复杂度。考试默认不说明时,一般讨论最坏情况。很多算法在最坏情况下和最好情况下差异很大,比如在顺序表末尾插入元素是O(1),在顺序表表头插入需要移动所有元素,是O(n),两个场景下“插入”操作复杂度不同,讨论时必须明确场景。
常见复杂度从低到高大致是:O(1) < O(log2 n) < O(n) < O(nlog2 n) < O(n^2) < O(n^3) < O(2^n) < O(n!)。选择题经常让排序,建议自己推导而不只是背顺序,推导方法是看当n翻倍、取10、取100时,执行次数增长多少。比如O(log2 n)在n取1024时只算10步,O(2^n)在n取30时已经超过10亿步,这种直观体感比口诀有用得多。
5.2 从三组高频代码里练出分析手感
第一组,单层循环但步长翻倍。看这段代码:
c复制int i = 1, count = 0;
while (i <= n) {
count++;
i = i * 2;
}
基本语句是count++和i = i * 2。i的变化序列是1、2、4、8……当循环执行k次后,i = 2^k。循环停止条件是i > n,所以2^k > n,解出来k > log2 n。基本操作执行次数大约是log2 n次,时间复杂度是O(log2 n)。这里的易错点是,很多同学一看到while就觉得是O(n),没注意变量不是每次加1而是翻倍变化。只要发现循环变量以乘法方式逼近边界,就要立刻想到对数复杂度。
第二组,双重循环但内层循环次数受外层影响:
c复制int sum = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
sum++;
}
}
内层循环次数随外层i变化,第一次执行1次,第二次执行2次,最后一次执行n次,总共执行1 + 2 + 3 + ... + n次,这是等差数列求和,等于n(n+1)/2。取最高阶并去掉系数,时间复杂度是O(n^2)。很多同学看到双重循环直接写O(n^2),这个例子里凑巧结果一样,但计算逻辑才是关键。如果内层循环条件是j <= n - i,总次数会变成n + (n-1) + ... + 1,仍然是O(n^2),但如果你背公式“双重循环就是O(n^2)”就可能忽略某些特殊题型的细微差别。
第三组,递归循环的空间复杂度:
c复制int fact(int n) {
if (n <= 1) {
return 1;
}
return n * fact(n - 1);
}
每次递归调用都会在栈上生成一个活动记录,递归深度是n,所以空间复杂度是O(n),时间复杂度也是O(n)。这个例子的意义在于提醒你:递归的空间复杂度不能忽略调用栈的消耗,一个看起来只定义了一个变量的递归函数,它所占的栈空间可能是O(n)甚至更高。
我再补充一个处理复杂度的实用经验:先把“循环变量的变化规律”写成数学表达式,再代入循环边界,最后只留最高阶。这三步缺一不可。数学表达式的形式可能是累加、累乘、递推,对应着求和公式、幂次关系、递归方程。第一轮练习不用追求解超高阶递推,能把王道书上的基础题用这个三步法做下来就够了。
5.3 空间复杂度里的“原地工作”怎么判断
王道第一章明确提出空间复杂度概念,后面许多算法题会强调“要求空间复杂度为O(1)”或“要求尽可能少用额外空间”。理解空间复杂度的关键是分清什么是问题本身的存储,什么是额外申请的辅助空间。
如果算法只需要根据问题规模申请一个同样大小的数组,那这个数组属于存储输入数据所需要的空间,不计入额外辅助空间。只有在原输入之外另开的辅助空间才需要统计。比如交换一个数组中的两个元素,借助一个临时变量tmp,这个tmp就是常数个辅助单元,空间复杂度O(1),称为“原地工作”。
但如果是把一个数组倒置,你新建了一个大小相同的数组用来拷贝原数组数据,这就要额外申请n个存储单元,空间复杂度O(n),不属于原地工作。
有个容易被忽略的点:函数递归调用产生的栈空间是否计入空间复杂度。我的建议是默认计入,特别是题目明确指出是递归算法时。考研算法题里经常出现“用递归实现,要求空间复杂度尽量低”的表述,此时如果后续用层数很深的递归实现,可能不满足题意。看到这类要求时,心里要有这根弦。
6. 第一章错题本:专治各种“看着眼熟却选错”的判断
6.1 判断概念对错的三把尺子
第一轮做题最容易出现的不是“完全不懂”,而是“感觉自己懂了,一做就错”。我开始复习时也是这样。后来总结出三个专门用来排雷的问题,每次遇到拿不准的判断,就依次问自己。
第一,题目里涉及的概念是不是位于正确的层级?比如问“最小单位”,要立刻意识到是数据项而不是数据元素,哪怕“数据元素”在另一个场合确实是基本单位。两个词单独看都眼熟,但一旦换到“最小”“基本”这种位置,答案就完全不同。
第二,题目里的条件和结论是不是可逆?比如“顺序存储支持随机存取”为真,但反过来问“凡是可以随机存取的结构一定是顺序存储”就未必为真,因为借助索引其他存储结构也可能实现近似随机定位,这种把充分条件当必要条件的命题方式在选择题里极其常见。
第三,题目说的是算法的五个特性,还是衡量算法优劣的标准?这是最容易踩的坑。算法的五个必要特性是:有穷性、确定性、可行性、输入、输出。而正确性、可读性、健壮性、高效率与低存储需求这几点,是“好算法”的评价标准,不是定义一个算法必须具备的特性。命题人经常把“可读性”混进五个特性里,让表述看起来很像教材原文,实际违反了概念的层次。
6.2 十道高频判断题,我先给答案再给原因
下面这些判断可以说覆盖了王道第一章课后选择题八成以上的概念考点。每道题我有意识地写得像是容易判错的样子,你可以先自己心里判断对错,再看解析。
1. 数据元素是数据的最小单位。 错误。数据元素是数据的基本单位,数据项才是数据的最小单位。这组说法是押题大户,必须一字不差地记住。
2. 数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 正确。这是教材原定义,重点是“特定关系”。如果题目改成“数据结构是数据元素的集合”,不完整,不算正确。
3. 相同的逻辑结构只能采用相同的存储结构。 错误。逻辑结构抽象描述关系,存储结构是实现方案,线性表既可以顺序存储,也可以链式存储,两者并不绑定。
4. 算法可以没有输入,但必须至少有一个输出。 正确。算法的输入可以为0,比如计算某个固定数的阶乘不需要外部输入;但输出必须至少有一个,因为算法的目的是解决问题,无输出的过程没有意义。
5. 算法必须满足可读性和健壮性。 错误。一个正确算法必须具备五个特性,即有穷性、确定性、可行性、输入、输出。可读性和健壮性是评价算法优劣的标准,不是算法存在的必要条件。
6. 时间复杂度为O(2n)的算法与时间复杂度为O(n)的算法性能完全一样。 这句话在严格的量级意义上是对的。大O记号要忽略常数系数,O(2n)和O(n)属于同一阶,都记作O(n)。第一轮有人会认为2n比n慢了一倍,不能划等号,但复杂度分析关注的是增长趋势,不关心精确的执行次数。
7. 顺序存储结构的所有数据元素一定在物理上相邻。 正确。这是顺序存储的核心定义,逻辑相邻的元素在内存地址上也是连续的。如果物理上不连续,那就是另一种存储方式了。
8. 链式存储结构比顺序存储结构一定更节省空间。 错误。链式需要额外指针空间,但在实际场景中,顺序存储一次性申请很大的连续空间如果没被完全利用,浪费可能更大。空间优劣必须在具体场景下讨论。
9. 散列存储通过散列函数直接计算出元素的存储地址。 正确。这是散列存储的基本思想,但要注意,冲突是可能的,判断里如果加了“一定不会发生冲突”那就是错的。
10. 数据的逻辑结构独立于存储结构,因此逻辑结构相同时,无论采用哪种存储结构,运算效率也一定相同。 错误。前半句正确,但后半句不成立。存储结构不同会导致同一个逻辑操作的实现方式差异很大,顺序表取第i个元素是O(1),链表取第i个元素是O(n),这就是典型例子。
做完这类题后,建议把做错的题抄在错题本上,旁边标注错误点属于哪种类型:概念层级错位、条件结论倒置、还是特性与评价混淆。这种整理方式比单纯把正确选项抄一遍有效得多,因为后面复习时,你只需要翻看错题原因分类,就知道自己的薄弱点在哪里。
6.3 概念题做题顺序,决定你避坑成功一半
做概念选择题,建议按顺序执行:先读题干最后一句话,明确问的是逻辑结构、存储结构还是算法复杂度;再回题目找“关系词”,是“一对一”“一对多”“多对多”还是“无关系”;然后想清楚题目讨论的是抽象层面还是实现层面,一旦涉及实现,多半在考存储结构。
我见过很多同学做错,不是知识点不会,而是没看清题目问的是“逻辑结构”却满脑子想着“存储结构”。概念章题目字面相近,选项看起来都能自圆其说,一旦读题方向偏了,后面越分析越乱。先锁定问题的维度,再调用对应知识,能极大降低错误率。
7. 第一轮复习节奏:我踩过的时间管理坑,请你绕开
7.1 推荐“概念理解+题后回看”的两遍过法
很多人第一轮会把第一章拖得很久,原因不是内容难,而是觉得东西太多不知道怎么抓重点。我自己的经验是:先花半天把整章通读一遍,不需要逐字背,目标是能画出前面说的三条线,并能说出“数据结构=逻辑结构+存储结构+运算”这句话的含义。
通读之后不要急着做太多题,先做王道课后选择题里那些概念判断题。做题时允许翻书,但每道题必须在旁边标出题号对应的教材知识点位置。做完对答案后,把错题对应的原文段落重新看一遍。这里有个关键的复习细节:只看正确答案不够,还要标出错误选项错在哪里,因为很多错题是命题人故意把两个相似概念打散重组的,如果你不标记错误点,下次遇到类似选项还是会掉坑。
7.2 我在“算法与程序的区别”上浪费过整晚
我第一轮复习犯过一个大错误:在一道题目上钻研了太久。那道题讨论“算法和程序的区别”,教材原文很清楚,算法必须满足有穷性,程序不一定满足。我却跑去想“操作系统不是一直运行吗,它算不算算法”之类的问题,结果越钻越深,偏离了考试语境。
吃过这个亏之后,我给自己定下一条规矩:第一轮复习中,凡是某个抽象定义与做题无关的延伸疑问,先记在笔记本最后,等整章刷完再回来看。数据结构是应用学科,考试考的是你能否把概念用到具体结构上,而不是考你对某个哲学边界的思辨能力。如果你在看王卓老师的网课,讲到这里时也可以适当倍速,重心始终放在“这个知识点会怎么出题”上。
7.3 一页纸浓缩第一章,后续复习全靠它
第一轮结束时,最好做一张一页纸总结,内容不用多,够你后续每周回看一次就行。我自己的版本大概是这样几条:
- 概念层:数据是符号集合,数据元素是基本单位,数据项是最小单位,数据对象是性质相同元素的集合。
- 结构层:逻辑结构管关系,有集合、线性、树、图;存储结构管实现,有顺序、链式、索引、散列。
- 复杂度层:复杂度分析三步走,找基本语句、数执行次数、留最高阶去系数。
- 易错点:算法的五特性不包含可读性;顺序存储支持随机存取而链式不支持;O(1)最小但只在规模可忽略时成立。
每次学完第二章的线性表、第三章的栈和队列,都可以回头对照这一页纸,看新学的结构到底属于哪一类逻辑结构、用了哪种存储方式、各种操作的复杂度是多少。你会发现,所有后续内容都是第一章框架的具体化展开。
真正吃透第一章的人,不是能背出定义的人,而是能在学完后面所有章节后,还能用第一章的语言解释每一章在干什么的人。把第一章当成一张地图而不是一块砖头,后面学习树、图、查找、排序时,你会感谢现在愿意花时间把概念边界抠清楚的自己。
