资讯动态

严蔚敏数据结构习题集C语言答案:从可编译代码到算法思维

发布时间:2026/9/18 5:39:43 来源:尧图企业网站定制
简介严蔚敏《数据结构(c语言版)习题集》全答案是一份面向计算机专业学生、考研备考者及自学者的经典配套资料围绕C语言版教材的章节习题提供完整解答覆盖绪论、线性表、栈与队列、串、树、图、查找与排序等核心内容。资源为单个PDF文件大小仅431KB以文本形式按章编排方便检索和打印也适合在手机、平板或电脑上随时查阅。至今已有10389人学习下载在数据结构学习群体中具有较强的实用口碑。答案不仅给出可直接运行的C语言源码还针对冒泡排序、动态规划求斐波那契序列、结构体与枚举统计成绩、数组越界处理、霍纳法则求多项式等典型题目说明算法思路与复杂度帮助读者既会写代码又能讲清原理。对于正在攻克数据结构难关的读者来说这是一份极具针对性的参考答案。1. 严蔚敏《数据结构(C语言版)习题集》全答案到底该怎么用严蔚敏《数据结构(C语言版)》配套的那本习题集几乎是计算机专业里人手一本的必刷题。网上流传的“全答案.pdf”版本很多但大多数只是把参考代码和文字解释拼在一起没有工程意义上的可验证性。真正能用的答案应该是一套能编译、能跑、能对边界条件做断言检查的C语言实现。这套题覆盖了线性表、树、图、查找、排序全部经典算法和面试里高频出现的数据结构与算法题直接相关。我的做法是把它当练习题库先自己写再对照这份答案然后动手把答案改写成自己的代码。这样得到的不是一个PDF而是一个能放进简历和代码库的东西。适合正在准备课程考试、考研复试以及数据结构与算法面试的人快速定位重点。这里要强调一句答案有没有价值先看它能不能编译通过。2. 把严蔚敏数据结构习题集的考点拆成C语言可执行单元2.1 从真题反推线性表、栈队列、串与数组的考法习题集里的线性表题目答案表面上是一堆Status ListInsert(...)之类的函数实际上考的是抽象数据类型的实现能力。做这类题我一般会先问算法的时间复杂度到底产生在哪一环顺序表插入要移动元素是 O(n) 的移动链表插入找到前驱是 O(n) 的查找插入动作本身才是 O(1)。答案里如果只写“时间复杂度 O(n)”是不够的要能说出是移动还是查找。栈和队列的题通常是括号匹配、表达式求值、循环队列判空判满。这里的答案不只要给出结构体定义还要把“队空front rear”和“队满(rear 1) % maxSize front”两个条件写清楚。我自己写这部分答案时习惯把结构体、初始化、入队、出队四个函数压缩在一个文件里先让它在main里跑通一组确定数据再讨论其他。串和数组的题目里KMP 是最难写对的一块。网上答案里的next数组实现版本很多有的下标从 0 开始有的从 1 开始直接抄很容易在边界上栽跟头。我的建议是在答案文件的注释里先注明“下标基准”再用一组字符串把next的每个值手算出来做对照。这样即使版本不同也知道错在哪。2.2 树与图答案里必须能跑通的递归和遍历框架树的题答案绝大多数都可以归结为三种递归遍历的变体。习题集里出现频率最高的是先序、中序、后序的递归与非递归写法以及层序遍历的队列实现。递归版要能默写下来void preorder(BiTree T) { if (T NULL) return; visit(T); // 先访问根节点 preorder(T-lchild); preorder(T-rchild); }非递归的时候先序和中序共用一套“一路向左”的框架区别只在出栈后是否立刻访问节点后序则需要记录上一次访问的节点或者用两个栈实现。答案里如果直接把非递归先序抄成后序几乎必然产生重复输出或死循环。写完后用三层二叉树在纸上按顺序走一遍栈的变化比盯代码更有效。图的题集中在邻接矩阵和邻接表的 DFS、BFS以及最小生成树和最短路径。BFS 的答案核心是“队列 visited 数组”DFS 的核心是“递归 visited 数组 连通分量数量统计”。注意习题集里的图节点编号一般从 1 开始而 C 语言数组从 0 开始答案代码里要不要统一减一是第一个要决定的事不然图一多就乱。2.3 查找与排序习题集里反复出现的复杂度边界查找部分顺序查找和二分查找的代码谁都会写习题集真正喜欢考的是“查找失败时的比较次数”和“ASL 计算”。二分查找答案要写清楚是左闭右闭还是左闭右开这直接影响while条件和mid更新int binary_search(int *a, int n, int key) { int lo 0, hi n - 1; // 左闭右闭区间 while (lo hi) { int mid lo (hi - lo) / 2; if (a[mid] key) lo mid 1; else if (a[mid] key) hi mid - 1; else return mid; } return -1; }排序部分是整本习题集里答案长度最夸张的。快速排序的多种分区写法、堆排序的建堆和调整、二路归并的哨兵设置每章的答案版本都不太一样。我的核对表是复杂度一半以上的答案错在把不稳定排序写成稳定或者在最好情况下还写 O(n²)。把这些排序算法按复杂度分好类一眼就能看出答案有没有写错排序算法平均时间最坏时间空间稳定性直接插入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 log n)O(n log n)O(1)不稳定归并O(n log n)O(n log n)O(n)稳定希尔排序的平均复杂度在不同教材里说法并不统一我个人倾向写“取决于增量序列”。快速排序的最坏情况出现在每次分区都极端不平衡时比如固定取第一个元素而输入已经有序这样退化成 O(n²)答案里如果不提这个前提复杂度分析就不完整。3. 手写C语言答案从最小可编译代码到边界测试3.1 以“合并两个有序链表”为例构造可验证的答案网上那份 PDF 里的链表题答案经常给一个很长的函数却没有配套的构造链表和打印函数。这样的答案只能看不能跑。我会把它改成最小可验证单元#include stdio.h #include stdlib.h typedef struct Node { int val; struct Node *next; } Node; Node* mergeTwoLists(Node *a, Node *b) { Node dummy {0, NULL}; // 栈上哑节点避免单独处理头指针 Node *tail dummy; while (a b) { if (a-val b-val) { tail-next a; a a-next; } else { tail-next b; b b-next; } tail tail-next; } tail-next a ? a : b; // 把剩余链表直接接上 return dummy.next; }代码的逻辑不复杂哑节点把“插入的第一个节点是头节点”这个特殊情况统一掉循环里每次比较两个候选节点的值谁小谁接上去。这里把写成会改变相等元素的稳定顺序写答案时要留意。函数参数 a、b 是两个非递减链表的头指针返回值是合并后链表的头指针。时间 O(nm)空间 O(1)因为没有申请新节点。做完这个核心函数后还需要配套的buildList、isSorted、freeList。常见做法是放进同一个test.c让每次改动都能立即编译验证。只写一个孤零零的mergeTwoLists既不能验证也不能体现对链表的整体理解。3.2 用断言和随机数据验证答案而不是肉眼检查对照 PDF 上的答案时肉眼看一次只能验证一组数据。我会在main里用assert加随机测试一次跑上千组#include assert.h #include time.h int cmpInt(const void *a, const void *b) { return *(const int*)a - *(const int*)b; } Node* buildList(int *arr, int n) { Node head {0, NULL}, *tail head; for (int i 0; i n; i) { tail-next (Node*)malloc(sizeof(Node)); tail tail-next; tail-val arr[i]; tail-next NULL; } return head.next; } int isSorted(Node *head) { while (head head-next) { if (head-val head-next-val) return 0; head head-next; } return 1; } int listLen(Node *head) { int n 0; while (head) { n; head head-next; } return n; } void testOne(int *a, int na, int *b, int nb) { Node *m mergeTwoLists(buildList(a, na), buildList(b, nb)); assert(isSorted(m)); assert(listLen(m) na nb); freeList(m); } int main(void) { int a[] {1, 3, 5}; int b[] {2, 4, 6}; testOne(a, 3, b, 3); srand((unsigned)time(NULL)); for (int t 0; t 1000; t) { int n rand() % 20, m rand() % 20; int *arrA (int*)malloc(n * sizeof(int)); int *arrB (int*)malloc(m * sizeof(int)); for (int i 0; i n; i) arrA[i] rand() % 100; for (int i 0; i m; i) arrB[i] rand() % 100; qsort(arrA, n, sizeof(int), cmpInt); qsort(arrB, m, sizeof(int), cmpInt); testOne(arrA, n, arrB, m); free(arrA); free(arrB); } puts(all tests passed); return 0; }这段代码和答案里的函数拼在一起编译就能把每次改动变成可重复的验证。参数说明buildList把数组转成链表isSorted检查合并结果是否仍然非递减listLen验证没有丢节点。随机测试里qsort先保证两个链表自身有序从而隔离链表合并函数本身的正确性。assert在定义了NDEBUG宏时会失效所以调试时不要加这个宏。3.3 习题答案里最常见的3个编译期错误与参数陷阱第一是“返回局部变量地址”。有些答案在函数里定义一个Node *p tmp;然后返回p调用瞬间数据就是垃圾值。遇到这种代码直接判定为不可用。第二是“修改头指针但形参传错”。删除值为 x 的节点时如果函数原型是void deleteNode(Node *head, int x)函数内部即使把head更新了外面的head也不会变。正确做法是传二级指针void deleteNode(Node **head, int x)或者让函数返回新的头指针。第三是“malloc 之后没有判断是否失败”这个在课程作业里不容易出事但在答案里属于习惯问题。练习时保持if (p NULL) { perror(malloc); exit(EXIT_FAILURE); }比等 valgrind 报错更直接。还有一类问题是答案用了非标准头文件比如#include conio.h或#include malloc.h。前者在 Linux、macOS 的 gcc 下直接找不到文件后者应该写成标准的#include stdlib.h。整理全答案时我会顺手把所有非标准头文件统一掉否则这套答案只能在 Windows 的某个 IDE 里运行出了这个环境就崩。4. 报告与实验源码把答案变成能交的作业成果4.1 用头文件、Makefile 与测试入口把答案工程化严蔚敏习题集的答案大多按章组织但在真实课程里老师要求交的是实验报告和可编译源码。我一般会按下面这样组织目录data-structure/ ├── include/ │ ├── list.h │ └── tree.h ├── src/ │ ├── list.c │ ├── tree.c │ └── main.c ├── tests/ │ ├── test_list.c │ └── test_tree.c ├── Makefile └── report.md头文件里只放结构体定义和函数声明src里放实现tests里放测试入口。这样可以先把 PDF 上的答案填进src/*.c再通过Makefile统一编译CC gcc CFLAGS -Wall -Wextra -g -stdc11 -fsanitizeaddress,undefined LDFLAGS -fsanitizeaddress,undefined test: tests/test_list.c src/list.c $(CC) $(CFLAGS) -Iinclude $^ -o $ $(LDFLAGS) clean: rm -f test *.o这里-Wall -Wextra打开警告-g保留调试信息-fsanitizeaddress,undefined让数组越界、非法访问在运行时直接崩溃而不是悄悄出错。把test作为 Makefile 的第一个目标默认make就能跑全部测试。如果是在 Windows 的 Visual Studio 环境就把src里的.c文件手动加进工程效果一样。提示在开启-fsanitizeaddress的情况下valgrind 可以不跑因为 ASan 已经能抓住越界和非法访问两者同时开会让程序运行速度明显变慢。4.2 用 gdb 和 valgrind 验证C语言答案的内存安全当测试失败时先看是不是断言失败再看是不是崩溃地址。gdb 可以定位到具体行号gdb --args ./test (gdb) break mergeTwoLists (gdb) run (gdb) print a-val (gdb) btbreak设断点run启动print看链表节点值bt打印调用栈。如果断言先失败可以用continue跳过前面的数据或者用condition命令在特定输入上触发断点。内存问题则统一交给 valgrindvalgrind --leak-checkfull --show-leak-kindsall ./test输出里definitely lost后面的字节数是真正需要修复的泄漏still reachable一般是程序结束时全局指针未释放课程作业里可以先放过。遇到Invalid read/write of size 4时把#0那一行地址记下来回源码找对应内存访问通常答案里某个tail tail-next在空链表上多走了一步。4.3 实验报告的写法从题目分析到复杂度表格实验报告不需要把整个.c文件贴进去那样反而显得没有重点。数据结构实验报告的重点是“题目分析、算法设计、核心代码、复杂度分析、测试结果”这五段式结构章节要写的东西建议篇幅题目分析输入输出范围、约束条件、边界情况半页算法设计文字描述思路定义不变量一页以内核心代码只贴关键函数每行或每块配注释两页以内复杂度分析时间、空间并指出瓶颈操作半页测试结果输入数据、运行输出、异常处理半页我在写报告时会把 2.3 节那张复杂度表直接引用到“复杂度分析”里再补上自己实测的数据量级。比如链表合并题在 100 万个节点下跑一次的时间和理论复杂度互为印证。这一套下来PDF 上的散装答案就变成了能拿得出手的实验代码。5. 背答案不如背思路用变式题检验真正掌握5.1 把答案改成ADT接口题检验抽象能力严蔚敏版教材有个特点链表、二叉树、图都先给 ADT 定义再给具体实现。全答案里如果只写算法函数名不看前面的抽象接口背下来也没用。拿着同一份 PDF 自测时我会故意把题目条件改掉把“两个带头结点的有序链表”改成“两个不带头结点的链表”把“升序合并”改成“降序合并”看原来的答案需要动多少行。动得越少说明抽象得越好动得越多说明当时只是在背形式。改题时有个技巧优先改“存储结构”而不是“逻辑结构”。比如把顺序表答案改成链表实现把二叉链表改成三叉链表把图的邻接矩阵改成邻接表。这样数据结构本身不变但 C 语言表达的指针变化把绝大多数抄答案的人卡住。这个过程比重复刷十遍原题更有用。5.2 用英文教材或同一题的多解对比检验掌握对比解法也是一种验证用 LeetCode 21 的合并函数或者《数据结构、算法与应用 C语言描述》里对应的习题和严蔚敏版本对照。不同教材对“带头结点”和“不带头结点”的定义不同会导致代码差异我在看答案时会在代码顶部注释里标注这两类前提。最后给一个很实用的做法把答案的关键代码折叠起来只留题目原话用自己的话重写一遍。如果重写版本和答案的结构完全一致说明看懂了如果只是停留在一两个循环变量的细节差异上说明答案确实变成了自己的东西。能用“动指针”和“改结构”两句话讲清合并链表的做法才算真正掌握了这道题的答案。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价