Skip to content

大学数据结构:初学者的难点与知识点全面总结

数据结构是计算机专业的核心基础课,也是许多人第一道「劝退」门槛。这门课难在它不是单纯背知识点,而是要求你同时具备抽象思维、逻辑推理和动手编码三种能力。本文从一个过来人的角度,把课程的重难点、核心知识点和踩坑经验整理成一份通关攻略。

一、为什么数据结构这么难

很多同学第一周还觉得简单,到链表和递归就开始懵,到树和图直接放弃。难点主要集中在这几处:

1. 指针与引用(C 语言用户感受最深)

链表、树、图全都依赖指针操作。初学者的经典崩溃现场:

c
// 链表插入的经典错误:顺序颠倒
p->next = q->next;   // 先保存 q 的后继
q->next = p;         // 再让 q 指向 p

难点本质:指针是「地址」,你要时刻在脑子里区分「这个变量本身」和「它指向的东西」。画图是唯一解药。

2. 递归思想

递归是数据结构的第一道坎,也是贯穿全课程的灵魂。斐波那契、二叉树遍历、快速排序、图的 DFS,全离不开它。

c
int fib(int n) {
    if (n <= 1) return n;       // 递归出口
    return fib(n - 1) + fib(n - 2);
}

难点本质:人类习惯「一步一步想」,而递归要求你「相信子问题已经解决」。很多同学卡在「它到底是怎么一层层展开的」,其实只要抓住两件事:递归出口递归关系,其余交给函数自己。

3. 抽象思维

课程会不断抽象:栈是「后进先出」,队列是「先进先出」,树的遍历顺序……难的不是实现,而是看到实际问题能想到用什么结构。比如:函数调用靠栈、打印机任务靠队列、表达式求值靠栈、目录结构是树。

难点本质:从「具体问题」到「数据结构」的建模能力,这是刷题和考试大题都在考的核心能力。

4. 复杂度分析(O 记法)

第一次见到 O(n)O(log n)O(n log n) 时,很多人会怀疑人生。其实它考的就是一件事:

当数据规模 n 变大时,算法耗时/内存会怎么增长?

常见复杂度从快到慢:

O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)

难点本质:不是数学,而是「抓大头」的能力——n 足够大时,低阶项和常数都可以忽略,只关心最高阶。

5. 动手能力

数据结构是「看得懂 ≠ 写得出来」的典型课程。看懂了双向链表的删除,自己写三遍照样报段错误(segmentation fault)。必须亲手敲代码,这一步无法跳过。

二、核心知识点全面梳理

下面按教材的经典章节顺序,把整本书的知识点过一遍,标注了重点(★必考 / ☆常考)。

1. 绪论与复杂度分析 ★

  • 数据结构三要素:逻辑结构、存储结构、数据的运算
  • 逻辑结构:线性结构(线性表、栈、队列、串)和非线性结构(树、图、集合)
  • 存储结构:顺序存储、链式存储、索引存储、散列存储
  • 时间复杂度、空间复杂度分析(大 O 记法)
  • 常见复杂度排序(见上文),以及最好/最坏/平均情况的分析

2. 线性表 ★★

线性表是最基础也最常考的结构,重点在顺序表和链表的对比

对比项顺序表(数组)链表
随机访问O(1),直接下标O(n),需从头遍历
插入/删除O(n),要移动元素O(1)(找到位置后)
空间需要连续空间,有扩容开销不需连续,但有指针开销
缓存友好性

必须掌握的链表操作:

  • 单链表的头插法、尾插法建表
  • 单链表的查找、插入、删除(注意顺序!)
  • 双链表、循环链表的插入删除
  • 经典习题:链表反转、找倒数第 k 个节点、判断是否有环(快慢指针)

考试必考大题之一:「写一个函数,删除链表中所有值为 x 的节点」这类题,务必练到随手能写。

3. 栈与队列 ★★

  • 栈(Stack):后进先出 LIFO。只允许在栈顶操作。应用:函数调用、表达式求值(中缀转后缀)、括号匹配、浏览器后退
  • 队列(Queue):先进先出 FIFO。应用:排队系统、消息队列、树的层次遍历、广度优先搜索
  • 栈的两种实现:顺序栈(注意栈满判断)、链栈
  • 队列的两种实现:循环队列(重点!)判断空/满的三种方法(牺牲一个单元、计数器、标志位)、链式队列
  • 双端队列:两端都能插入删除
  • 经典习题:两个栈实现队列、两个队列实现栈、括号匹配

括号匹配是栈的「Hello World」,把它彻底搞懂,栈就入门了。

4. 串与 KMP 算法 ★

  • 串的存储:顺序存储为主
  • 子串查找:朴素模式匹配(BF 算法,O(n·m))
  • KMP 算法(难点中的难点):利用 next 数组让匹配失配时主串指针不回溯,将最坏复杂度降到 O(n+m)
  • 重点理解 next 数组的推导,以及改进版 nextval

学习技巧:KMP 别先看代码,先画两张表——主串和模式串,手动走一遍失配过程,理解「为什么可以跳」。

5. 树与二叉树 ★★★(全课程最重要的章节)

树是考试分值占比最大的一章,必须重点攻破。

基础概念

  • 树的基本术语:度、孩子、双亲、兄弟、深度、高度
  • 二叉树的性质(必背必考):
    • 第 i 层至多有 2^(i-1) 个节点
    • 深度为 k 的二叉树至多有 2^k - 1 个节点
    • 叶子数 n0 与度数为 2 的节点数 n2 的关系:n0 = n2 + 1
    • 具有 n 个节点的完全二叉树深度为 ⌊log2n⌋ + 1
  • 满二叉树、完全二叉树、二叉排序树、平衡二叉树的概念

二叉树的遍历 ★★

遍历方式访问顺序记忆口诀
先序遍历根 → 左 → 右根左右
中序遍历左 → 根 → 右左根右
后序遍历左 → 右 → 根左右根
层次遍历逐层从左到右用队列

必须会:递归写法(容易)和非递归写法(用栈模拟,困难但必考)、根据两种遍历序列唯一还原二叉树(经典大题)。

线索二叉树

让空指针域指向遍历前驱/后继。了解概念即可,会画图,理解「线索」的含义。

树与森林

  • 树的存储:双亲表示法、孩子表示法、孩子兄弟表示法
  • 树、森林与二叉树的相互转换(左孩子右兄弟是核心)
  • 树的先根遍历 = 对应二叉树的先序遍历,后根遍历 = 中序遍历

哈夫曼树(最优二叉树)★

  • 带权路径长度 WPL,WPL 最小的二叉树是哈夫曼树
  • 哈夫曼编码:左 0 右 1,前缀编码(任一编码不是另一编码的前缀)
  • 会手工构造哈夫曼树、计算 WPL

二叉排序树 BST 与平衡二叉树 AVL ★★

  • BST:左子树 < 根 < 右子树,中序遍历得到有序序列
  • BST 的查找、插入、删除(删除三种情况:叶子、单子树、双子树)
  • AVL 平衡因子:|左子树高度 - 右子树高度| ≤ 1
  • 四种旋转:LL、RR、LR、RL(画图记忆,考试大题必考)

堆 ★

  • 大根堆、小根堆:根节点是最大/最小的完全二叉树
  • 堆的插入(上浮)、删除(下沉)
  • 堆是优先队列的实现,也是堆排序的基础

6. 图 ★★★(最抽象的一章)

图是思维跳跃最大的一章,很多人在这章掉队。核心是抓住「存储 → 遍历 → 算法」这条主线。

图的存储 ★★

存储方式说明特点
邻接矩阵n×n 二维数组简单直观,适合稠密图,O(n²) 空间
邻接表每个顶点一个链表省空间,适合稀疏图
  • 会画、会建、会读:给定矩阵/表能还原图
  • 有向图、无向图、带权图(网)的表示

图的遍历 ★★

  • DFS 深度优先:递归(或栈),一条路走到黑,像「走迷宫」
  • BFS 广度优先:队列,一层层扩散,像「水波扩散」
  • 都要记住:visited 数组防重复访问,以及复杂度都是 O(n + e)

最小生成树 MST ★

  • Prim 算法:从一个点出发,每次选离「树」最近的边,适合稠密图
  • Kruskal 算法:按边权从小到大选边,不成环就选,适合稀疏图
  • 判断是否成环:并查集(简单了解即可)

最短路径 ★

  • Dijkstra 算法(单源最短路径,重点必考):贪心思想,每次选当前距离最近的未访问点。要求边权非负
  • Floyd 算法(任意两点):动态规划,三重循环,边权可为负但不能有负环

拓扑排序 ★

  • 有向无环图(DAG)的顶点线性排序,每次删除入度为 0 的顶点
  • 应用:课程安排的先后顺序、工程项目的依赖关系
  • 判断图是否有环:拓扑排序能排完 = 无环

7. 查找 ★★

  • 顺序查找:O(n),无要求
  • 折半查找(二分):O(log n),要求有序 + 顺序存储,会画判定树
  • 二叉排序树查找:O(log n) ~ O(n),取决于树是否平衡
  • 哈希查找(重点):
    • 哈希函数构造:除留余数法(最常用)、直接定址法、数字分析法
    • 冲突处理:开放定址法(线性探测、平方探测、再哈希)、链地址法
    • 装填因子 α = 表中记录数 / 表长,α 越大冲突越多
    • 会计算平均查找长度 ASL(成功 / 失败,常考大题)

8. 排序 ★★★(必背考点密集区)

必须掌握每种排序的算法思想、代码框架、复杂度、稳定性,以及一张总结表。

排序算法平均时间最坏时间空间稳定性备注
直接插入O(n²)O(n²)O(1)稳定简单,适合基本有序
希尔排序O(n^1.3)O(n²)O(1)不稳定插入的改进,分组插入
冒泡排序O(n²)O(n²)O(1)稳定交换相邻逆序对
快速排序O(n log n)O(n²)O(log n)不稳定分治+枢轴,平均最快
简单选择O(n²)O(n²)O(1)不稳定每轮选最小
堆排序O(n log n)O(n log n)O(1)不稳定建堆+交换+调整
归并排序O(n log n)O(n log n)O(n)稳定分治,需要辅助空间
基数排序O(d(n+r))O(d(n+r))O(n+r)稳定按位分配收集,不比较

必须掌握

  1. 快速排序的手写过程(每一轮的分区结果)——考试大题最爱考
  2. 堆排序的建堆和调整过程(画树)
  3. 快排的优化思路(三数取中、随机枢轴)防止退化成 O(n²)
  4. 各算法「最好/最坏」情况分别是什么输入(如快排最坏 = 基本有序)

9. 其他章节(了解即可)

  • 并查集:集合合并与查询,应用在 Kruskal、连通分量
  • 外部排序:多路归并、败者树,了解思想
  • 文件结构:顺序文件、索引文件、散列文件,了解概念

三、高频考点例题精讲

知识点梳理得再清楚,考试最终还是落在「会做题」上。这一节精选了期末考试/考研中最常考的例题,每题都给出完整解题过程。建议先自己动笔做一遍,再看解析,效果最好。

例 1:复杂度分析

题目 1:求下列代码的时间复杂度:

c
for (int i = 1; i <= n; i++)
    for (int j = i; j <= n; j++)
        x++;

解析:内层循环总次数 = n + (n-1) + … + 1 = n(n+1)/2,所以时间复杂度为 O(n²)

题目 2:已知 T(1) = 1,T(n) = T(n-1) + n(n > 1),求 T(n)。

解析:逐层展开:

T(n) = T(n-1) + n
     = T(n-2) + (n-1) + n
     = …
     = T(1) + 2 + 3 + … + n
     = n(n+1)/2

所以 T(n) = O(n²)

例 2:链表——删除单链表中所有值为 x 的节点(代码大题)

题目:单链表 L 带头节点,设计算法删除所有值为 x 的节点,并分析时间复杂度。

参考答案

c
void deleteAll(LinkList L, ElemType x) {   // L 带头节点
    LNode *p = L->next, *pre = L;
    while (p != NULL) {
        if (p->data == x) {        // 找到目标节点
            pre->next = p->next;   // 先接线,后释放
            free(p);
            p = pre->next;
        } else {                   // 非目标节点,正常后移
            pre = p;
            p = p->next;
        }
    }
}

解析:用 pre 始终指向 p 的前驱。删除时记住口诀「先接线,后释放,p 回到 pre 的 next」。每个节点只处理一次,时间复杂度 O(n)

高频变形:删除倒数第 k 个节点(快慢指针)、反转链表(三指针)、有序链表去重,这三种务必也能默写。

例 3:栈——中缀表达式转后缀表达式

题目:将中缀表达式 A + B * (C - D) - E / F 转为后缀表达式。

规则:数字直接输出;运算符与栈顶比优先级,高则入栈,低或相等则弹出;左括号直接入栈,遇到右括号则弹出直到左括号为止。

输入操作输出序列
A直接输出A
+入栈A+
B直接输出A B+
*高于 +,入栈A B+ *
(入栈A B+ * (
C直接输出A B C+ * (
-( 内无运算符,入栈A B C+ * ( -
D直接输出A B C D+ * ( -
)弹出到 ( 为止A B C D -+ *
-弹出 *、+,再入栈A B C D - * +-
E直接输出A B C D - * + E-
/高于 -,入栈A B C D - * + E- /
F直接输出A B C D - * + E F- /
结束全部弹出A B C D - * + E F - /

答案:后缀表达式为 A B C D - * + E F - /

例 4:二叉树——由遍历序列还原二叉树

题目:已知某二叉树的先序遍历为 A B D E C F,中序遍历为 D B E A C F,还原该二叉树并写出后序遍历。

解题步骤

  1. 先序第一个节点 A 是根
  2. 在中序中找 A:左边 D B E 是左子树,右边 C F 是右子树
  3. 左子树先序 B D E、中序 D B E → 根 B,左孩子 D,右孩子 E
  4. 右子树先序 C F、中序 C F → 根 C,右孩子 F(无左孩子)

还原结果:

        A
       / \
      B   C
     / \   \
    D   E   F

答案:后序遍历为 D E B F C A

核心口诀:先序定根,中序分左右。只有「先序+中序」或「后序+中序」能唯一还原;只给先序和后序无法唯一确定。

例 5:哈夫曼树——构造、WPL 与编码

题目:字符集 {a, b, c, d, e} 的权值分别为 {5, 9, 12, 13, 16},构造哈夫曼树,求 WPL 并写出各字符的哈夫曼编码。

解题步骤:每次取两个最小的权值合并,重复直到只剩一棵树:

  1. 5 + 9 = 14 →
  2. 12 + 13 = 25 →
  3. 14 + 16 = 30 →
  4. 25 + 30 = 55
            55
          /    \
        25      30
       /  \    /  \
      12  13  14   16
            /  \
           5    9

WPL = 12×2 + 13×2 + 16×2 + 5×3 + 9×3 = 24 + 26 + 32 + 15 + 27 = 124

编码(左 0 右 1):a(5) = 100,b(9) = 101,c(12) = 00,d(13) = 01,e(16) = 11

平均编码长度 = WPL / 总权值 = 124 / 55 ≈ 2.25 位/字符

注意:合并顺序不同树形可能不同,但 WPL 相同;验证编码正确与否,看「任一编码都不能是另一编码的前缀」。

例 6:平衡二叉树——AVL 的插入与旋转

题目:依次向空树插入 50, 30, 70, 20, 40, 35,画出每次插入后的 AVL 树。

解题步骤

插入 50 → 30 → 70 → 20 → 40 后,树保持平衡:

        50
       /  \
     30    70
    /  \
  20    40

插入 35(40 的左孩子)后,节点 50 的平衡因子变为 |3 - 1| = 2,失衡。新节点在 50 的左孩子(30)的右子树上,属于 LR 型,需要两次旋转。

第一步:对 30 左旋(以 40 为轴):

     30              40
    /  \            /  \
  20    40   →    30    -
       /          / \
     35         20  35

第二步:对 50 右旋(以 40 为轴):

        50              40
       /  \            /  \
     40    70   →    30    50
    /               / \     \
  30              20  35    70
  / \
20  35

最终结果

        40
       /  \
     30    50
    /  \     \
  20   35    70

记忆口诀:LL 右单旋,RR 左单旋,LR 先左后右,RL 先右后左。考试必考「插一个数,画出旋转过程」,务必在纸上反复练习找「旋转轴」。

例 7:快速排序——排序过程模拟

题目:对序列 49 38 65 97 76 13 27 49 按升序快排,枢轴取第一个元素,写出第一趟排序结果。

解题步骤(i 从左往右找比枢轴大的,j 从右往左找比枢轴小的,交替填坑):

枢轴 = 49(取出后位置 0 空出):

步骤操作序列状态
1j 向左找到 27 < 49,填入空位 027 38 65 97 76 13 [ ] 49
2i 向右找到 65 > 49,填入空位 627 38 [ ] 97 76 13 65 49
3j 向左找到 13 < 49,填入空位 227 38 13 97 76 [ ] 65 49
4i 向右找到 97 > 49,填入空位 527 38 13 [ ] 76 97 65 49
5j 左移与 i 相遇,枢轴归位27 38 13 49 76 97 65 49

第一趟结果27 38 13 | 49 | 76 97 65 49

左半 27 38 13(枢轴 27)一趟后为 13 27 38;右半 76 97 65 49(枢轴 76)一趟后为 49 76 65 97。继续递归分区,最终得到升序序列:13 27 38 49 49 65 76 97

快排每趟确定枢轴的最终位置,且左边元素都小于它、右边都大于它。这趟过程是填空/大题的最爱,务必自己完整模拟一遍。

例 8:图——Dijkstra 单源最短路径

题目:以 0 为源点,求到各顶点的最短路径。图用邻接表表示((顶点, 权值)):

0: (1,10) (4,5)
1: (2,1)  (4,2)
2: (3,4)
3: (0,7)  (2,6)
4: (1,3)  (2,9) (3,2)

解题步骤:维护 dist[] 数组,每轮选「未访问的最近顶点」并更新其邻居的 dist。

轮次选中dist[1]dist[2]dist[3]dist[4]说明
00105从 0 出发
14814754→1 得 5+3=8 < 10;4→3 得 7
238137-3→2 得 7+6=13 < 14
3189--1→2 得 8+1=9 < 13
42-9--全部确定

答案

顶点最短距离路径
180 → 4 → 1
290 → 4 → 1 → 2
370 → 4 → 3
450 → 4

注意:Dijkstra 只适用于边权非负的图。考试常考「用表格写出每轮 dist 变化」,这类题练三遍就能拿稳。

例 9:哈希表——构造与平均查找长度

题目:关键字序列 {19, 14, 23, 1, 68, 20, 84, 27, 55, 11, 10, 79},表长 m = 13,散列函数 H(key) = key % 13,用线性探测处理冲突,构造哈希表并求查找成功的 ASL。

解题步骤:依次计算散列地址,冲突则向后探测(下标 0~12 循环):

关键字H(key)探测过程存放位置比较次数
1966 空61
1411 空11
231010 空101
111 冲突 → 2 空22
6833 空31
2077 空71
8466、7 冲突 → 8 空83
2711、2、3 冲突 → 4 空44
5533、4 冲突 → 5 空53
111111 空111
101010、11 冲突 → 12 空123
7911~8 冲突 → 9 空98

哈希表

0123456789101112
-14168275519208479231110

ASL(查找成功) = (1+1+1+2+1+1+3+4+3+1+3+8) / 12 = 29 / 12 ≈ 2.42

高频变形:改用「链地址法」处理冲突(每个槽位挂链表)、求查找失败的 ASL、计算装填因子 α = 12/13。线性探测「聚集现象」的缺点也要能说上来。

四、学习方法与建议

1. 画图!画图!画图!

数据结构是「用图思考」的学科。准备一个本子(或白板、GoodNotes),做任何一道题都先画:

  • 链表操作:画出插入/删除前后的节点指向
  • 树的遍历:画出递归调用栈的进出过程
  • 图的算法:画出 visited 数组、dist 数组每一步的变化

凡是画得出图的知识点,基本就学懂了;凡是画不出图的,一定是还没懂。

2. 每个结构手写一遍代码

看懂了 ≠ 会写。建议每一个结构都按这个流程走:

  1. 不看任何资料,白纸写一遍结构定义和基本操作
  2. 对照教材改错
  3. 在编辑器里敲出来,编译运行
  4. 故意改错几个地方(比如把 p->next 写成 q->next),观察报错现象

语言选择:多数学校用 C/C++ 教学,就用 C/C++ 学;如果你 C 语言没学好,强烈建议先补 C 的指针,否则后面的链式结构全是空中楼阁。

3. 复杂度分析要「背框架,算特例」

不要试图死记每个算法的复杂度,而是理解「为什么」:

  • 循环嵌套一层 = 大概率 O(n²)
  • 每轮规模减半 = O(log n)
  • 分治且合并要 O(n) = O(n log n)

然后记住几个特例:快排最坏 O(n²)、希尔 O(n^1.3)、基数排序与数据范围相关。

4. 刷题策略:先课后题,再 OJ,最后竞赛题

阶段目标建议
课内吃透教材例题和课后题每章至少独立做完 80% 的课后题
OJ 入门洛谷 / PTA 的基础题按数据结构专题刷,不贪多,每题吃透
进阶LeetCode 简单~中等面试和工作通用,长期受益

经典必刷题清单(对应本章知识点):

  • 链表:LeetCode 206 反转链表141 环形链表21 合并两个有序链表
  • 栈/队列:20 有效的括号232 用栈实现队列
  • 树:94 二叉树的中序遍历102 层序遍历104 最大深度
  • 图:200 岛屿数量(DFS/BFS)、207 课程表(拓扑排序)
  • 查找/排序:215 数组中的第 K 个最大元素(快排思想)、704 二分查找

5. 善用可视化工具

  • VisuAlgo(visualgo.net):动画演示排序、图算法、哈希表,强烈推荐
  • CS USF 交互式演示:数据结构和算法动画
  • 手写过程模拟:期末复习时,把快排、Dijkstra、哈夫曼编码在纸上完整跑一遍

6. 知识点要用「主线」串起来复习

期末复习不要按章节零散背,按主线走:

线性表/栈/队列(线性结构基础)

树(非线性 + 递归巅峰)

图(非线性 + 算法密集)

查找(树 + 哈希,综合运用)

排序(全课程算法思想总检阅)

7. 常见坑位提醒

  • 别只背代码:考试大题考的是过程模拟和思想,代码背得再熟,不会推导照样丢分
  • 别眼高手低:看到题「哦我会了」→ 关掉答案写一遍 → 卡壳 → 这才是真实的水平
  • 别跳过课后题:很多期末大题就是课后题的变形
  • 别轻视复杂度:面试必问,考研必考,平时就要养成分析的习惯

五、结语

数据结构这门课,难是难,但它也是「投入产出比」最高的课之一:学懂了,你会第一次感受到「用计算机的思维方式解决问题」的乐趣;学懂了,后面的操作系统、计算机网络、算法设计与分析都会轻松很多。

最后送上一句话:数据结构没有天赋流,只有图量和代码量。 画够一百张图,敲够一千行代码,这门课就通关了。加油!如果这篇文章对你有帮助,后续我会继续更新 KMP、AVL 旋转、图算法等重难点的专题讲解,敬请期待。