第二周是数据结构这门课第一个真正的分水岭。我见过太多人在第一周还信心满满,觉得“不就是一门编程课嘛”,结果第二周学完线性表和复杂度分析,直接开始怀疑自己是不是适合学计算机。原因很简单:数据结构考察的不是“会不会写代码”,而是“能不能抽象地思考数据之间的关系和组织方式”。这种思维转换,恰恰是第二周课程内容真正想让你完成的。
这篇文章就是基于第二周数据结构的学习主线,结合我当年踩过的坑和后来辅导学弟学妹时反复遇到的问题,聊一聊这周到底该怎么学、学到什么程度算“会了”、以及哪些地方是期末和考研的高频考点。无论你是正在跟着学校进度走,还是打算自学《王道数据结构》或者李春葆老师的教材,这篇文章的思路都能直接拿来用。
1. 第二周的学习坐标系:先搞清楚你在哪条路上
很多同学第二周学得痛苦,不是因为内容难,而是因为根本没弄清自己该学什么。不同的教材、不同的学校、不同的目标,对应的重点完全不一样。如果你一上来就照着《王道考研数据结构》猛啃,同时又想跟上学校的实验报告进度,很快就裂开了。
1.1 先判断你的学习路线属于哪种
我复盘了一下,数据结构学习者基本分三条路线:
第一条是考研应试线,代表资料是王道考研系列。这条线的特点是极其强调时间复杂度分析、代码填空题、算法设计题的套路。比如同样讲链表,王道会更关注“怎么用O(1)空间实现逆置”“怎么判断链表是否有环”这类高频考题,对工程实现细节反而不太抠。
第二条是本科教学线,代表是学校指定的教材,常见的有李春葆的《数据结构》、严蔚敏的经典C语言版、或者一些学校自编讲义。这条线更强调“每种结构都能手写C语言实现”,实验报告、上机考试都围绕这个展开。第二周大概率会让你写顺序表或者单链表的增删改查。
第三条是工程应用线,侧重点是Python、Java里的内置数据结构,比如Python的list、dict是怎么实现的,pandas的DataFrame底层结构怎么影响性能。C语言手写链表在这条线里反而不是重点。
这三条线没有高低之分,但混着学必死。我见过一个同学用王道的思路去写学校实验报告,结果因为没按要求实现“带头结点的尾插法构建链表”,直接被扣了十几分。反过来,用学校教材的节奏去准备考研,效率又太低。
1.2 第二周的典型内容范围
虽然各学校进度有差异,但第二周的内容基本落在三个板块:
- 算法复杂度分析:大O记号的含义、时间复杂度的计算、空间复杂度的判断
- 线性表:顺序表的实现与操作、单链表的实现与操作、两种结构的复杂度对比
- 栈和队列的引入:部分学校进度快,第二周已经讲到了栈的顺序存储和链式存储,甚至提了一嘴双端队列
也就是说,第二周其实是在给你打“地基中的地基”。后面第三周开始学栈和队列的进阶应用、第四周学字符串和数组,全部默认你已经把链表操作练到“闭着眼能写出来”的程度。如果这一周你只是“听懂了”而没有“写出来”,后面会非常被动。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 时空复杂度:第一个真正的思维门槛
所有的数据结构教材,开头都会先讲复杂度分析。这个板块看起来最简单,实际上是个隐形杀手。很多学生觉得“这有什么好学的,不就是数循环次数吗”,结果一写算法设计题就错,一分析递归就懵。问题出在思维方式没有转换过来。
2.1 大O记号不是“运行次数”,而是“增长趋势”
我第一次学时间复杂度的时候,特别纠结一个问题:一个循环执行100次,另一个执行n次,为什么都说O(1)和O(n)?后来才明白,大O描述的是当输入规模n趋近于无穷大时,运行时间的增长速度。
打个比方,你开了两家快递站。一家每天固定处理100个包裹,另一家每天处理的包裹数等于城市人口n。如果城市只有50个人,前者看起来比后者忙;但如果城市有100万人、1000万人呢?前者还是100个,后者的工作量会线性暴涨。大O就是用来描述这种“长期趋势”的,而不是某个具体点的快慢。
因此,判断时间复杂度时,常数系数可以被忽略。3n、100n、10000n,在大O记号里统统写成O(n)。第二周很多人拧巴的地方就在这——明明循环跑了3次,怎么时间复杂度是O(1)?没错,因为不管n多大,它都只跑3次,这就是常数级的特征。
2.2 空间复杂度的计算,最容易翻车
热词里专门有“数据结构与算法 空间复杂度”,说明这个知识点困扰了很多人。空间复杂度判断的一个典型误区,是把“数组本身占据的空间”也算成“额外空间”。
比如题目要求“原地逆置一个长度为n的数组”,你开了一个临时变量temp用于交换元素,那么额外空间就是O(1)。如果你又新建了一个等长数组来存逆置结果,空间复杂度就是O(n),这样的实现通常不会得分。
还有一种容易漏的情况是递归栈空间。写递归函数时,每次递归调用都会在系统栈里占用一份空间。比如求n!的递归函数,递归深度是n,所以空间复杂度是O(n)。哪怕你的函数体里一个数组都没开,空间复杂度也不是O(1)。第二周如果不建立这个意识,等你学到二叉树深度相关的递归分析时,会彻底晕掉。
2.3 一个实用的复杂度速算方法
做复杂度分析时,我后来总结出一套很机械化的流程,推荐你直接照抄:
- 看循环变量:如果i从0变到n,步长为1,那么基本操作次数正比于n,O(n);
- 看嵌套循环:外层n次、内层n次,相乘得到O(n²);
- 看循环变量的更新方式:如果i每轮乘以2,那么执行次数是log₂n量级,O(log n);
- 看递归:写出递推式T(n)=T(n/2)+O(1),用主定理或者递归树判断;
- 看最内层基本操作:只数最深层循环内的操作,外层准备工作忽略。
这套方法能解决第二周90%的复杂度题目。剩下10%的偏题怪题,一般出现在考研题里,比如双重循环的j受i影响,这种需要老老实实累加求和。
3. 线性表:顺序表和链表的八个关键差异
第二周课程内容的绝对核心,就是线性表。几乎所有的数据结构教材,都会在这一周把顺序表和单链表对比着讲。很多同学学完后的状态是:顺序表会写,链表能看明白,但“为什么要用链表”完全说不出来。如果你也是这个状态,说明你还没有真正理解线性表。
3.1 顺序存储的底层逻辑:一块连续内存的代价
顺序表本质上是“用数组模拟线性关系”。逻辑上相邻的元素,物理地址也相邻。这个特性带来两个直接好处:
- 随机访问能力强:按下标找第i个元素,直接算地址,O(1)
- 空间利用率高:不需要额外的指针字段
但代价也很明显:
- 插入和删除要移动元素:在长度为n的顺序表第i位插入,平均移动n/2个元素,O(n)
- 扩容很昂贵:数组满了以后要重新申请内存并复制所有元素
“数组扩容”这一点,很多同学学到后面会忘,但Python的list、C++的vector之所以能动态增长,底层都是这个逻辑。学数据结构最好的副产品,就是你看待高级语言内置容器时会多了一层“原来如此”的感受。
3.2 单链表的核心操作:画图比写代码更重要
单链表的代码量不大,但指针操作的先后顺序非常致命。大多数人第一次写链表插入时,都会出现“链断了”的bug。比如要在p节点后面插入新节点s:
c复制s->next = p->next;
p->next = s;
这两行代码的顺序不能颠倒。如果先执行p->next = s,那么p原来的后继节点就丢了,s就找不到它了。我当时学的时候,老师反复强调“先接后面,再改前面”,但真正让我记住的,是第一次上机时眼睁睁看着整个链表少了一半元素。
我的建议是:学链表的第一周,不要急着写代码,先在纸上把每一步画出来。画出p、s节点,画出next指针的方向,然后模拟“断开连接、建立新连接”的过程。画明白三个操作(头插法、尾插法、删除节点),再回到代码,你会觉得指针操作不过是纸上图景的翻译。
3.3 带头结点和不带头结点,到底差在哪
这是第二周最容易让新手崩溃的细节之一。同样是单链表,有的代码里有一个“头结点”(head指向一个不存数据的节点),有的代码直接用head指针指向第一个数据节点。
两者的核心差异在于:
- 带头结点:空表的判断条件是head->next == NULL;插入删除不需要单独处理“头指针变化”的情况,代码更统一
- 不带头结点:空表的判断条件是head == NULL;在头部插入或删除时,必须修改头指针本身,需要用到二级指针(C语言里是struct Node **head)
热词里专门有“数据结构链表”和“数据结构c语言版”这两个热搜词,说明很多人在学链表时倒在了C语言指针这座山前。给你一个定心丸:学数据结构时指针感到痛苦是正常的,这恰恰说明你在学习正确的东西。指针的难点不是语法,而是“变量存的是地址还是值”这个思维转换。链表是练习这个思维最好的载体,没有之一。
3.4 顺序表 vs 链表:一张表看清选型逻辑
第二周的课程内容虽然不要求你做出工业级的选型判断,但面试题和考研题都特别喜欢考察“什么时候用顺序表,什么时候用链表”。这里给你一个核心判断标准:
| 维度 | 顺序表 | 单链表 |
|---|---|---|
| 随机访问 | O(1) | O(n) |
| 头部插入/删除 | O(n),需移动所有元素 | O(1) |
| 尾部插入 | O(1)(不考虑扩容) | O(n)(需遍历到尾部) |
| 空间占用 | 紧凑,无额外指针开销 | 每个节点多一个指针字段 |
| 缓存友好性 | 高,连续空间 | 低,节点分散 |
| 适用场景 | 查多改少、规模已知 | 频繁增删、规模动态变化 |
记住一个口诀:查得多用顺序,改得多用链表。到了后面的课程你会看到,很多高级数据结构(比如哈希表、树)都是在这两种存储思想的基础上做组合和变体。
4. 栈、队列与双端队列:别被“抽象”吓住
进度快的学校,第二周会讲到栈和队列。你只要抓住一句话就够:栈和队列不是新的存储结构,它们是限制了操作方式的线性表。底层还是顺序表或链表那一套,只不过对外暴露的接口变了。
4.1 栈和队列是“接口”,不是“实现”
栈的特点是后进先出(LIFO),只允许在一端插入和删除。队列的特点是先进先出(FIFO),一端插入、另一端删除。
理解这个抽象后,你会突然明白很多高级语言设计的用意。比如函数调用时的系统栈,就是典型的栈结构——后调用的函数先返回。操作系统的任务调度队列,就是典型的队列结构——先到达的任务先执行。第二周学的虽然只是简单的插入删除规则,但这是你理解后续所有算法(深度优先搜索用栈、广度优先搜索用队列)的基础。
4.2 循环队列的“浪费一个空间”是怎么回事
队列用顺序表实现时,会遇到一个尴尬的问题:反复入队出队后,队尾到数组末尾了,但数组前面全是空位。解决办法是把数组看成一个环,队尾越界后绕回开头,这就是循环队列。
循环队列的实现有两个关键约定:
- 判断队空的约定:front == rear
- 判断队满的约定:(rear + 1) % maxSize == front
正是因为空和满的条件不能一样,所以循环队列通常会浪费一个数组空间,让“队满”永远不会和“队空”条件一致。很多学生会问“为什么不加一个计数器来区分呢?”当然可以,而且教科书上有这种方案,但会额外增加一个变量的维护。用空间换代码简洁,这是数据结构的经典取舍。
4.3 双端队列:两端都能进能出的结构
热词里出现了“数据结构 双端队列”,这个知识点在第二周出现,通常是作为队列的扩展知识。双端队列(deque)允许在两端进行插入和删除,相当于栈和队列的混合体。
Python的collections.deque、C++ STL里的deque,都是这种结构的工程实现。它的典型应用场景包括:实现“最近最少使用”缓存策略、滑动窗口最大值问题(这个在学到算法时会遇到)、以及某些需要双端操作的调度场景。
第二周你不需要深入双端队列的代码实现,但需要理解它的四种基本操作:头部插入、头部删除、尾部插入、尾部删除。以及一个衍生结论:双端队列可以模拟栈(只用一端),也可以模拟队列(一头进另一头出)。这个推论在后面的算法题里会反复被用到。
5. 手写C语言实现时,我踩过的四个典型坑
数据结构这门课的实践环节,往往要求你用C语言实现书上的结构。很多同学看教材时觉得“都懂”,一开写就卡住,而且卡的点非常集中。我把自己踩过和辅导时见到的坑整理一下,希望你直接避开。
5.1 没有初始化就使用指针
最经典的错误:
c复制struct Node *p;
p->data = 5;
p只是一个野指针,根本不知道指向哪里,就给它赋值。这行代码编译时可能不报错,运行时才崩溃,而且是随机的——有时候能跑,有时候Segmentation fault。正确做法是先建立一个节点:
c复制struct Node *p = (struct Node *)malloc(sizeof(struct Node));
如果节点用完要释放,还要记得把指针置为NULL,避免悬空指针。
5.2 malloc之后忘记判断是否成功
C语言的malloc在内存分配失败时会返回NULL,但很多教材的示例代码里会省略这个判断。等到上机调试、数据量一大,malloc返回NULL,你的代码直接崩掉。
一个合格的数据结构实现,每次malloc之后都要检查:
c复制struct Node *p = (struct Node *)malloc(sizeof(struct Node));
if (p == NULL) {
printf("内存分配失败\n");
exit(1);
}
这个习惯看起来啰嗦,但它会在你后续学树、学图、学哈希表时保护你无数次。
5.3 链表的删除操作忘了释放节点
删除链表节点时,只把前驱节点的next指向被删除节点的后继,就算完成了吗?不算。被删除节点申请的内存还挂在系统里,如果没人释放,就会内存泄漏。
正确的删除步骤是:先保存待删除节点的后继,再把前驱连接过去,最后free掉待删除节点。顺序不能乱,不然提前free了,你后面访问它的next就会用已释放的内存。
5.4 传参问题:为什么链表头传不进去
很多同学第一次写不带头结点的链表时,写了个函数想往里插节点,结果调用完后主函数里的头指针还是NULL。原因是C语言参数传递是值传递,你在函数里修改的只是头指针的副本。
解决办法是传入头指针的地址,也就是二级指针:
c复制void insertHead(struct Node **head, int data);
调用时写成insertHead(&head, data)。这个知识点在热词“数据结构c语言版”里经常被搜到,因为它几乎是每个C语言版数据结构初学者必踩的一道坎。
6. 资料与工具:王道、李春葆、PDF资料和实验报告怎么搭配
第二周学习时,很多人手里同时有好几份资料:学校指定教材、王道考研书、网上下载的PDF讲义、学长学姐留下的实验报告。但资料越多,越容易迷茫。我根据自己的经验,给你一套搭配方案。
6.1 每类资料解决什么问题
**学校教材(比如李春葆版)**负责的是“完整性与规范性”。它的优势是知识点全面、配套习题多、和课程进度完全对齐。尤其是李春葆老师那套书,结构清晰,C语言代码风格规范,非常适合跟着课程节奏一章章读。但是,教材的缺点是重点不突出——它什么都讲,你不知道哪些是高频考点。
王道考研资料负责的是“重点与考点”。王道把每个知识点的考察频率、常见题型、易错点都标注得很清楚。它的代码题和复杂度的讲解方式,非常适合你第二周结束后来查漏补缺。但纯王道的问题在于代码实现比较简略,不是拿来上机跑的项目。
网上的PDF资料,比如一些大学公开课的课件、讲义,质量参差不齐。它们的价值在于多个视角理解同一个概念——如果你看不懂教材上对循环队列的解释,可以换个讲法再看一遍,往往就通了。但不要以PDF为主学习材料,太零散。
学长的实验报告是宝藏,但也是最危险的东西。说它是宝藏,因为它直接告诉你实验课的验收标准是什么、代码要交到什么程度、报告怎么写容易得高分。说它危险,因为直接抄报告等于放弃了自己动手的机会,而上机调试那些bug的体验,恰恰是数据结构学习中最有价值的部分。
6.2 我建议的第二周学习节奏
我把一个比较理想的节奏整理给你,每一步的时间可以根据你自己的进度调整:
- 课前不要预习太多,把教材对应章节的标题和代码扫一眼,标记看不懂的地方就行;
- 课上重点听复杂度分析和链表操作的讲解,这两个地方最容易出现“一听就懂、一写就废”;
- 课后当天把书上的代码亲手敲一遍,不管你觉得多简单,这一步不能省;
- 第一个周末用王道的对应章节做自测,把课后题做一遍,尤其是复杂度分析题;
- 第二个周末回到学校实验,检查自己的实现有没有内存泄漏、有没有处理空表情况。
每个人的状态不同,但大方向是:教材打底,王道点睛,实验验收,PDF当字典。千万不要反着来——用王道当教材,用教材当字典,那样会学得又累又漏。
7. 把第二周内容放到期末和考研的视角下去看
第二周的内容虽然只是数据结构这门课的起点,但它们对期末成绩和考研初试的影响,远超很多同学的想象。热词里有“数据结构期末复习”“数据结构考研”“数据结构王道”,说明大多数人关心的最终出口就是这两个。那我不妨直接把话说透。
7.1 期末怎么考:选择、填空与算法设计
期末考试中第二周内容的直接分值通常占15分到20分左右。题型一般是:
- 选择题:判断复杂度、选择顺序表和链表的适用场景
- 填空题:写出某段代码的时间复杂度、写明循环队列满条件
- 算法设计题:手写单链表的逆置、删除指定元素等经典操作
后半学期学到树、图、查找、排序之后,大量算法都建立在链表操作之上。举个例子,树的孩子兄弟表示法本质就是一个链表;图的邻接表表示法也是链表;哈希表的链地址法还是链表。如果链表操作不熟练,后面每次实验都会卡在同样的位置。
7.2 考研怎么考:从“手写代码”到“细致分析”
考研408统考对数据结构的考察,很少让你完整手写一个几十行的算法,而是更注重:
- 给一段代码,判断它的功能是什么
- 给一个数据结构设计,分析它的时间复杂度和空间复杂度
- 给一个算法思想,要求补全关键代码
这意味着你对第二周内容的理解,不能停留在“能写出来”,而要能准确描述为什么。比如:为什么单链表逆置可以用头插法?为什么用两个栈可以实现队列?这些问题在第二周如果认真追问过,后面冲刺阶段会节省大量时间。
7.3 给下周的你:第三周的衔接建议
第三周通常会进入栈和队列的应用、递归与分治、以及字符串匹配的初步内容。现在可以做的准备是:
- 把链表操作练到“不看书也能写出来”的程度,最好能边写边解释每行代码的用途;
- 认真做几道递归相关的题目(比如用递归求斐波那契数列、汉诺塔),因为递归会贯穿后面的树、图、分治算法;
- 把顺序表和链表的复杂度对比表自己推导一遍,不要背,要能现场推导出来。
数据结构的学习有一个特点:后面的难点从来不是后面的知识本身,而是前面欠的账。第二周欠下的链表操作和复杂度思维,会在第三周找你还。反过来,如果这一周你把顺序表、链表、复杂度这“三件套”真正啃透了,后面的课程对你来说只会越来越顺。
我个人在带过的每一届学生里都观察到,最终成绩好的那些人,不一定是最早开始刷题的,但几乎都是在第二周就养成了“亲手实现+画图推导+复杂度分析”三个习惯的人。这周的内容本身不复杂,复杂的是你要在一周内完成从“写代码”到“设计结构”的思维跃迁。别怕慢,慢慢来,比较快。
