news 2026/9/17 5:35:50

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
严蔚敏数据结构习题集C语言答案:从可编译代码到算法思维

简介:严蔚敏《数据结构(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(n+m),空间 O(1),因为没有申请新节点。

做完这个核心函数后,还需要配套的buildListisSortedfreeList。常见做法是放进同一个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 -std=c11 -fsanitize=address,undefined LDFLAGS = -fsanitize=address,undefined test: tests/test_list.c src/list.c $(CC) $(CFLAGS) -Iinclude $^ -o $@ $(LDFLAGS) clean: rm -f test *.o

这里-Wall -Wextra打开警告,-g保留调试信息,-fsanitize=address,undefined让数组越界、非法访问在运行时直接崩溃而不是悄悄出错。把test作为 Makefile 的第一个目标,默认make就能跑全部测试。如果是在 Windows 的 Visual Studio 环境,就把src里的.c文件手动加进工程,效果一样。

提示:在开启-fsanitize=address的情况下,valgrind 可以不跑,因为 ASan 已经能抓住越界和非法访问;两者同时开会让程序运行速度明显变慢。

4.2 用 gdb 和 valgrind 验证C语言答案的内存安全

当测试失败时,先看是不是断言失败,再看是不是崩溃地址。gdb 可以定位到具体行号:

gdb --args ./test (gdb) break mergeTwoLists (gdb) run (gdb) print a->val (gdb) bt

break设断点,run启动,print看链表节点值,bt打印调用栈。如果断言先失败,可以用continue跳过前面的数据,或者用condition命令在特定输入上触发断点。

内存问题则统一交给 valgrind:

valgrind --leak-check=full --show-leak-kinds=all ./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++语言描述》里对应的习题,和严蔚敏版本对照。不同教材对“带头结点”和“不带头结点”的定义不同,会导致代码差异;我在看答案时会在代码顶部注释里标注这两类前提。

最后给一个很实用的做法:把答案的关键代码折叠起来,只留题目原话,用自己的话重写一遍。如果重写版本和答案的结构完全一致,说明看懂了;如果只是停留在一两个循环变量的细节差异上,说明答案确实变成了自己的东西。能用“动指针”和“改结构”两句话讲清合并链表的做法,才算真正掌握了这道题的答案。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 5:35:34

COMSOL双温模型在激光材料加工中的仿真应用

1. 项目背景与核心价值在工程热物理和材料科学领域&#xff0c;高温环境下固体材料的传热与变形行为研究一直是个关键课题。当激光、等离子体等高能束作用于金属表面时&#xff0c;材料会经历复杂的双温效应&#xff08;电子温度与晶格温度不平衡&#xff09;、热弹性变形以及烧…

作者头像 李华
网站建设 2026/9/17 5:34:46

Java异步编程:CompletableFuture核心原理与实战优化

1. CompletableFuture 核心概念解析CompletableFuture 是 Java 8 引入的异步编程利器&#xff0c;它完美解决了传统 Future 的阻塞问题。我在处理电商订单异步处理系统时&#xff0c;发现传统 Future.get() 会导致线程无谓等待&#xff0c;而 CompletableFuture 的链式调用让异…

作者头像 李华
网站建设 2026/9/17 5:33:50

麒麟V10 SP1 Legacy BIOS PXE网络安装实战指南

1. 项目概述&#xff1a;为什么在国产麒麟系统上坚持用PXE做Legacy BIOS安装&#xff1f;最近三个月&#xff0c;我帮六家政企单位部署国产化办公终端&#xff0c;其中四家明确要求必须使用传统BIOS&#xff08;Legacy&#xff09;模式通过PXE网络启动安装银河麒麟V10 SP1&…

作者头像 李华
网站建设 2026/9/17 5:33:40

FME转换器参考手册实战:从数据流机制到性能调优

简介&#xff1a;《2022FME转换器快速参考手册&#xff08;中文版&#xff09;》是一份面向FME初学与日常操作人员的实用查询资料&#xff0c;按功能分类梳理数百个转换器的用途与应用场景&#xff0c;帮助读者在处理空间数据转换、要素重组和工作流搭建时快速定位合适的工具。…

作者头像 李华
网站建设 2026/9/17 5:32:28

Jetson边缘AI开发实战:从镜像烧录到YOLO部署的完整技术栈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华