ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

严蔚敏数据结构C语言代码落地指南:从理论到可运行

严蔚敏数据结构C语言代码落地指南:从理论到可运行 简介严蔚敏《数据结构与算法C语言》教材的配套代码实现合集面向计算机专业学生、考研复习者及需要提升算法功底的一线程序员。资源按教材章节体系组织涵盖线性表、栈与队列、树与二叉树、图、排序与查找、动态规划、贪心算法、回溯法等核心知识点的可运行源码每种数据结构均提供C与C双版本实现配有头文件与简单测试数据便于对照书本理论逐行理解。压缩包共416个文件以154个cpp、154个c源码及89个h头文件为主体附带少量txt说明与dat测试数据整体仅494KB体积轻量、结构清晰适合离线收藏与反复研读。已有1607人学习下载是严蔚敏经典教材的高频配套资源之一。拿到手即可编译运行边调试边体会栈、二叉树、最短路径等经典算法的实现细节将晦涩的理论真正转化为动手与应试能力。1. 严蔚敏《数据结构C语言版》的代码实现抄完书上的算法为什么还是跑不出结果严蔚敏《数据结构C语言版》是很多计算机专业学生第一本翻到卷边的教材但真正动手写代码时会发现一个尴尬局面书上的算法描述全都是类C风格的缺类型定义、缺头文件、甚至函数参数里还写着“L”这种C语言不支持的引用写法。直接照着抄编译就报错。这不是你基础差而是这本书本来就不负责给你一份能直接运行的工程代码你需要自己把“算法长什么样”翻译成“编译器能接受什么”。这篇文章就从顺序表开始一路覆盖链表、二叉树、KMP、堆排序这些高频模块讲清楚每个模块怎么改造成可编译、可运行的C代码顺手解决实验报告怎么写、期末和408复习怎么抓重点的问题。目标读者是准备数据结构实验、期末复习和考研的人新手能照着写熟手能避开常见坑。2. 从类C到可运行的C严蔚敏代码落地的第一步是改掉这几个习惯2.1 看懂Status、ElemType和函数头动手前先花十分钟做翻译严蔚敏书里的算法描述大量使用 Status、ElemType 这类抽象类型标识。在C语言里这些都是“不存在的”需要你提前通过 typedef 定义成真实类型。很多第一次上手的同学没意识到这一点照着抄完发现满屏“Status undefined”立刻心态崩了。其实这不是你的问题而是书的描述语言和你使用的编译器语言存在一层翻译工作。常见做法是先建一个公共头文件把这些记号统一翻译好/* common.h —— 严蔚敏代码的公共类型定义 */ #include stdio.h #include stdlib.h #include string.h typedef int Status; /* 函数执行状态用 OK/ERROR 表示成功失败 */ typedef int ElemType; /* 表里存的数据类型需要时改成 float、struct 等 */ #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2这段代码的核心是给“类型”套一层别名。Status 本质上就是 int返回值用宏定义做语义区分ElemType 更关键因为它决定了顺序表、链表的数据单元是什么。当你把一个存 int 的表改成存自定义结构体时只需要改这一行 typedef所有用到 ElemType 的地方自动跟着变。这种写法不是多余的它在告诉你数据结构代码应该和具体数据类型解耦。真正让很多人在编译期就卡住的是函数参数里的“引用”。书里写 ListInsert(L, i, e)在纯 C 环境下没有引用类型常见做法是把参数改成指针例如 ListInsert(SqList *L, int i, ElemType e)。如果你在某份参考代码里看到形参写的是 SqList *L调用处传的是 L那说明作者已经把类C语法翻译成指针语义了。反过来如果函数定义里收的是 SqList L调用处也传了 L那你对结构体做的所有修改在函数返回后都会被丢弃。这是 2.3 要展开说的坑。2.2 顺序表最小可运行版本从SqList结构体到插入函数一次过顺序表是全书第一个正式的数据结构也是几乎所有实验的基础。它的实现思路不复杂一块连续数组加一个 length 记录当前元素个数。难点在于插入和删除时元素移动的方向以及逻辑位序和数组下标的换算。先写一版最小但能编译的顺序表/* sqlist.c —— 顺序表初始化与插入 */ #define MAXSIZE 100 typedef struct { ElemType data[MAXSIZE]; int length; /* 当前元素个数 */ } SqList; /* 初始化只保留空表状态方便重复测试 */ Status InitList(SqList *L) { if (NULL L) return ERROR; L-length 0; return OK; } /* 插入i 从 1 开始表示逻辑位置第 i 个元素 */ Status ListInsert(SqList *L, int i, ElemType e) { int k; if (i 1 || i L-length 1) return ERROR; /* 越界检查 */ if (L-length MAXSIZE) return ERROR; /* 容量检查 */ for (k L-length; k i; k--) { L-data[k] L-data[k - 1]; /* 从后往前挪避免覆盖 */ } L-data[i - 1] e; L-length; return OK; }逻辑说明插入位置 i 按书上的习惯从 1 开始但 C 数组下标从 0 开始所以真正写入的位置是 data[i-1]。移动元素时一定从最后一个元素开始往前搬因为如果从前往后后面的元素还没搬走就被前面的覆盖了。参数 i 的范围判断是 i 1 且 i length1这个 length1 留了口子给“插到表尾”的场景不少第一次写的人会把上界写成 length导致只能在已有元素中间插入。为了能直接跑起来再加一个打印函数和 mainvoid PrintList(SqList *L) { int i; for (i 0; i L-length; i) printf(%d , L-data[i]); printf(\n); } int main(void) { SqList L; int arr[] {10, 20, 30}; int i; InitList(L); for (i 0; i 3; i) ListInsert(L, i 1, arr[i]); PrintList(L); return 0; }这段代码里最容易忽略的是 InitList(L) 里的 。SqList L 是一个结构体变量传给 InitList 时必须取地址函数内部才能通过 L-length 修改调用方的结构体。如果你写成 InitList(L)编译器会报类型错误就算编译过了修改也是白做。数组 arr 里的元素是从 1 号位置开始依次插入的所以循环里传入的位置是 i1插入之后表内顺序是 10 20 30不是 30 20 10这是顺序表区别于头插链表的地方。2.3 函数传参改指针为什么“函数运行完没有变化”是第一个翻车点很多初学者写完上面代码遇到的现象是main 里调用 InitList(L)函数内部 length 确实归零了但回到 main 一打印L.length 还是原来的值。原因很简单C 语言默认值传递函数拿到的是 L 的一份拷贝你在函数内部改的是这份拷贝原结构体没动。看一个最经典的对比/* 错误示范形参是普通变量交换只发生在函数内部 */ void bad_swap(ElemType a, ElemType b) { ElemType t a; a b; b t; } /* 正确示范形参是指针通过解引用修改调用方的变量 */ void good_swap(ElemType *a, ElemType *b) { ElemType t *a; *a *b; *b t; }bad_swap 不是算法写错了而是参数传递方式选错了。函数里 a 和 b 的交换确实发生了但这两个变量是 main 调用时把值拷贝进来生成的“临时替身”交换完就销毁对 main 里的原始变量毫无影响。good_swap 传的是地址函数内部通过 *a 和 *b 直接读写调用方的内存修改才会被保留。所以拿到严蔚敏书上任何一个函数先看它要不要对外部数据做修改。要修改就传指针不要修改可以按值传。这个习惯培养起来之后链表、二叉树、图的代码会少踩很多坑因为你写每个函数时都会先问自己一句这个参数是来读的还是来写的3. 四个高频模块的代码实现链表、KMP、二叉树与排序算法选讲3.1 单链表的头插、尾插与遍历带头结点的写法为什么最省心链表在实验报告里出镜率极高尤其是“头插法建表”和“尾插法建表”这种基础操作。严蔚敏书里用的是带头结点的设计头结点不存数据只用来统一空表和非空表的处理逻辑。没有头结点时往空表插入第一个节点要改头指针本身往非空表插入要改的是某个节点的 next两种场景得写两套判断。有了头结点后插入逻辑统一成“在某个节点后面挂新节点”代码量少一大截。/* linkedlist.c —— 带头结点的单链表 */ typedef struct LNode { ElemType data; struct LNode *next; } LNode, *LinkList; /* 头插法新节点永远插在头结点之后最终结果是逆序 */ LinkList CreateList_Head(ElemType a[], int n) { LinkList head (LinkList)malloc(sizeof(LNode)); int i; head-next NULL; for (i 0; i n; i) { LinkList p (LinkList)malloc(sizeof(LNode)); p-data a[i]; p-next head-next; /* 新节点指向原来第一个节点 */ head-next p; /* 头结点指向新节点 */ } return head; } /* 尾插法维护一个尾指针每次把新节点挂到尾部顺序保持 */ LinkList CreateList_Tail(ElemType a[], int n) { LinkList head (LinkList)malloc(sizeof(LNode)); LinkList tail head; int i; head-next NULL; for (i 0; i n; i) { LinkList p (LinkList)malloc(sizeof(LNode)); p-data a[i]; p-next NULL; tail-next p; tail p; } return head; }头插法代码里的顺序很重要先让 p-next 指向 head-next再把 head-next 指向 p。如果先把 head-next 赋给 pp 还没挂到链表上原来的下一个节点就丢了。尾插法多维护一个 tailtail 始终指向最后一个节点这样每次插入不需要从头遍历到尾部建表复杂度是 O(n)。验证是否成功写一个遍历函数把每个节点的 data 打出来头插输入 1 2 3 输出 3 2 1尾插输出 1 2 3两者一对比就理解了。注意 malloc 出来的每个节点next 一定要先赋值。头插法的 p-next 被赋为 head-next尾插法的 p-next 赋为 NULL。节点数据本身没多大风险风险全在 next 指针上一个未初始化的指针会让遍历函数在某个瞬间跳到非法地址程序崩溃时你根本查不到是哪一步出了问题。3.2 KMP算法next数组到底怎么算先弄懂前缀和后缀KMP 是数据结构面试和考研里反复出现的考点。它的核心思想是主串指针不回溯失配时模式串根据 next 数组跳到下一个可能匹配的位置。很多同学记住了算法流程却栽在 next 数组的计算上。next[j] 的含义是当模式串第 j 位失配时下一个用模式串第几位去和当前主串字符比较。这个值等于模式串 [0, j-1] 这个子串的“最长相等前后缀长度”。/* kmp.c —— KMP 字符串匹配下标从 0 开始 */ void get_next(const char *p, int next[]) { int len (int)strlen(p); int i 0, j -1; next[0] -1; while (i len - 1) { if (j -1 || p[i] p[j]) { i; j; next[i] j; } else { j next[j]; /* 匹配失败前缀指针回退 */ } } } int kmp_index(const char *s, const char *p) { int i 0, j 0; int slen (int)strlen(s); int plen (int)strlen(p); int next[256]; get_next(p, next); while (i slen j plen) { if (j -1 || s[i] p[j]) { i; j; } else { j next[j]; } } if (j plen) return i - j; /* 返回模式串在主串中的起始下标 */ return -1; }这个版本使用 0 下标和严蔚敏书里“串从 1 号下标开始存、next[1]0”的描述略有差异但核心思想一致。你在对比书上代码时要理解两种实现只是下标平移了一层不是算法不同。求 next 的过程本质上是用模式串自己和自己做匹配i 在“主串”位置j 在“模式串”位置相等就都前进不相等就让 j 回退到 next[j]和 KMP 主匹配流程是同构的。写 KMP 时最典型的错误是混淆长度和下标。比如模式串长度是 plen最后一个字符的下标是 plen-1循环条件写成 i len 就会越界访问 next[len]主串匹配成功判断条件是 j plen 而不是 j plen-1因为循环退出有两种可能一种是 j 走到头表示匹配完成一种是 i 走到头表示主串扫完。用“ababc”做模式串手算一遍 next 数组会让记忆深得多算出来的结果是 [-1, 0, 0, 1, 2]这个结果可以直接对到 get_next 的代码里验证。3.3 二叉树递归遍历一步到位中序非递归重点检查出栈时机二叉树遍历是数据结构实验里的固定项目。递归版本代码极短难的是非递归版本尤其是中序遍历。中序非递归的思路是从根开始一路往左走沿途节点全部入栈走到没有左孩子时出栈一个节点并访问然后转向它的右孩子再重复“一路往左入栈”的过程。/* btree.c —— 二叉树定义与中序遍历 */ typedef struct BiTNode { ElemType data; struct BiTNode *lchild, *rchild; } BiTNode, *BiTree; /* 递归中序遍历 */ void InOrder(BiTree t) { if (t NULL) return; InOrder(t-lchild); printf(%d , t-data); InOrder(t-rchild); } /* 非递归中序遍历栈数组保存节点 */ void InOrder_Stack(BiTree t) { BiTree stack[1024]; int top 0; while (top 0 || t ! NULL) { while (t ! NULL) { stack[top] t; /* 左路节点全部入栈 */ t t-lchild; } if (top 0) { t stack[--top]; printf(%d , t-data); /* 出栈访问 中序 */ t t-rchild; /* 转右子树 */ } } }递归版本的关键是“先判空再递归”少了这个 if传入空树时函数会继续访问 t-lchild对空指针解引用直接段错误。非递归版本的关键在于访问节点的时机中序要求在左子树处理完之后访问根所以外层循环每次从栈里弹出那个“左子树已经走完”的节点。栈数组固定 1024对深度不超过 1024 的二叉树够用但如果测试数据是一棵退化到 2000 层的单链树这种固定栈就会溢出。实验报告里可以注明“栈容量可改为动态扩展”这是一个很好的加分点。3.4 堆排序与双端队列算法题和实验报告里的一组常客排序算法是很多学校实验报告的必选堆排序在其中格外有代表性因为它同时考察数组操作和完全二叉树的理解。堆排序分两步先建堆再反复把堆顶和最后一个元素交换并调整。/* sort.c —— 堆排序 */ void sift(int a[], int low, int high) { int i low, j i * 2 1; /* j 是左孩子 */ int tmp a[i]; while (j high) { if (j 1 high a[j] a[j 1]) j; /* 选出左右孩子中较大的 */ if (tmp a[j]) { a[i] a[j]; i j; j i * 2 1; } else { break; } } a[i] tmp; } void heap_sort(int a[], int n) { int k; for (k n / 2 - 1; k 0; k--) /* 从最后一个非叶子开始 */ sift(a, k, n - 1); for (k n - 1; k 0; k--) { int t a[0]; a[0] a[k]; a[k] t; sift(a, 0, k - 1); /* 缩小范围重新调整 */ } }建堆为什么从 n/2-1 开始因为完全二叉树中下标 n/2-1 是最后一个非叶子节点比它大的下标全是叶子叶子本身已经满足堆性质不需要调整。排序阶段每次把最大的堆顶换到尾部然后缩小堆的范围重新让堆顶下沉。堆排序的平均时间复杂度是 O(n log n)不稳定这一点在考研题里经常被问到。双端队列是“数据结构 双端队列”搜索里的高频词。它的实现通常有两种双向链表和环形数组。链表实现直观但代码长环形数组节省空间。下面是一版环形数组的双端队列牺牲一个数组槽位来区分队空和队满/* deque.c —— 环形数组双端队列 */ #define QUEUE_MAX 8 typedef struct { ElemType data[QUEUE_MAX]; int head, tail; /* head 指向队首元素tail 指向队尾的下一个位置 */ } Deque; void init_deque(Deque *q) { q-head 0; q-tail 0; } /* 从队头插入 */ int push_front(Deque *q, ElemType e) { if ((q-tail 1) % QUEUE_MAX q-head) return ERROR; /* 队满 */ q-head (q-head - 1 QUEUE_MAX) % QUEUE_MAX; q-data[q-head] e; return OK; } /* 从队尾弹出 */ int pop_back(Deque *q, ElemType *e) { if (q-head q-tail) return ERROR; /* 队空 */ q-tail (q-tail - 1 QUEUE_MAX) % QUEUE_MAX; *e q-data[q-tail]; return OK; }环形数组的重点是取模运算。head 往前移动时要加 QUEUE_MAX 再取模因为 head-1 可能变成负数C 语言的负数取模结果不是我们期望的正数。队列判空用 head tail判满用 (tail1) % MAX head相当于约定数组里永远空一个位置。如果你看到别人用 head tail 既判空又判满那说明他用了额外变量记录元素个数两种方案各有利弊但“用一个空槽位”是初学者最容易理解、最不容易写错的方案。4. 实验报告与复习的两条路线一边能交差一边能应付考试4.1 数据结构实验报告怎么写才不容易被打回四个板块讲清从题目到代码的完整链路很多人觉得实验报告就是把代码一贴、截个图就完事。但大多数课程对报告格式有要求尤其数据结构这种核心课老师会看你的结构体设计、算法描述和测试用例。常见的高分报告结构是四个板块需求分析、设计说明、运行结果、调试心得。需求分析要写明“输入是什么、输出是什么、边界条件有哪些”而不是复述题目原话。设计说明里放关键数据结构定义和核心函数思路比如顺序表这块就要说明为什么 insert 的位置从 1 开始、为什么移动元素从后往前。运行结果要贴真实输出不能只贴代码。调试心得写自己踩过的坑比如“一开始传参忘记用指针导致初始化无效”这类记录老师非常认可。表格里可以这样规划报告板块写什么常见错误需求分析输入范围、输出格式直接抄题目设计说明结构体定义、算法时间复杂度只有代码没有解释运行结果正确的截图和输出只贴代码不贴输出调试心得遇到的问题与解决过程写“没有问题”这一栏背后是让学生真正跑过代码而不是把网上的代码抄一遍。实验报告的价值不在形式而在于它逼着你把“能跑的代码”和“能说清的思路”对应起来。4.2 从“5*5鞍点”看暴力枚举与剪枝报告里的优化痕迹怎么写有一套常见题是这样输入一个 5x5 矩阵找出所有“鞍点”——该位置在它所在行是最大值在它所在列是最小值。很多人第一反应是暴力枚举所有位置对每个位置再扫描行和列这种写法能过但对大规模矩阵性能很差。用 C 语言实现时可以先写一版最直接的做法再优化成一个可复测的版本。/* saddle.c —— 计算 5*5 矩阵中的鞍点 */ #include stdio.h #include limits.h int main(void) { int a[5][5]; int i, j, k; int found 0; for (i 0; i 5; i) for (j 0; j 5; j) scanf(%d, a[i][j]); for (i 0; i 5 !found; i) { for (j 0; j 5; j) { int row_max 1, col_min 1; for (k 0; k 5; k) { if (a[i][k] a[i][j]) row_max 0; if (a[k][j] a[i][j]) col_min 0; } if (row_max col_min) { printf(鞍点: a[%d][%d]%d\n, i, j, a[i][j]); found 1; } } } if (!found) printf(未找到鞍点\n); return 0; }暴力枚举的思路很清晰每一个位置都做一次“行扫描 列扫描”时间复杂度 O(n^3)。进一步优化可以先用两个数组 row_max[i] 和 col_min[j] 预存每行最大值和每列最小值然后再一次两层循环判断多个条件复杂度降到 O(n^2)。这类“先预处理再主查找”的思路其实就是算法设计里的空间换时间。剪枝算法在这个问题里表现为“提前结束不可能的分支”。比如发现某一行已经有两个位置同时竞争最大值或者某一列已经出现更小值就不用继续枚举。在更复杂的搜索场景里剪枝是暴力枚举法最实用的优化手段。实验报告里把暴力版和优化版都写上再对比时间复杂度比单纯贴一个最终代码更有说服力。4.3 期末和408复习视角图和数组为什么总被单独拎出来考数据结构期末和408统考里图和数组是两道分值不低的硬骨头。数组这块考的是“存储地址计算”和“特殊矩阵压缩”——比如二维数组按行优先存储时a[i][j] 的地址怎么算对称矩阵、三角矩阵怎么压缩成一维数组。图这边考的是邻接矩阵和邻接表建图、DFS/BFS 遍历顺序、最小生成树、最短路径。这些东西最大的特点是“代码不好写但计算题爱考”。复习时建议两条腿走路先手画图再手写代码。画图题能训练“从邻接表还原图结构”的直觉代码题能训练“把结构转换成指针和数组”的能力。408真题里经常给一个图的邻接表让你写出从某个顶点出发的深度优先遍历序列这种题如果你不熟悉“遍历时访问哪些邻接点、标记数组怎么用”很容易丢分。数组地址计算的通用方法是按行优先时地址 起始地址 (i * 列数 j) * 元素大小。关键是 i 和 j 从 0 开始还是从 1 开始题目里经常埋坑。图的 DFS 代码遵循“访问后立即标记”的原则防止重复访问递归版本要注意系统栈可能不够深考试时通常会先问“画出递归过程”而不是要求你现场调栈。5. 避坑专项把严蔚敏书的算法搬到C语言这五个坎最多人踩5.1 顺序表从书上搬到C第一个翻车点是数组下标现象是照着书上代码把插入函数写出来循环从 1 到 length结果数据总是错位。原因是严蔚敏书里顺序表的位序默认从 1 开始而 C 数组下标从 0 开始书上的 L.elem[i] 对应 C 里的 L.data[i-1]。如果直接把 i 当数组下标插入位置和实际存储位置永远差一位。解决方法是明确区分“逻辑位序”和“物理下标”。逻辑位序是给用户看的插入第 1 个位置进入数组后放在 data[0]。写循环时用 k 从 length 到 i访问数据时统一做 data[k-1]。也可以在结构体里多分配一个元素让 data[1] 开始存数据data[0] 空着这样逻辑和物理完全对齐但不推荐因为会浪费内存且破坏习惯。5.2 函数运行完没变化传值传递让修改丢得干干净净现象是InitList(L) 调用后main 里 L.length 仍然是随机值。原因是函数参数是值传递函数内部拿到的是 L 的拷贝修改拷贝对原结构体无任何影响。C 语言里结构体还能整个传值但数组会自动退化成指针两类参数的行为不一致容易让初学者搞混。解决方法是凡是要修改结构体内容一律传指针。函数定义写 InitList(SqList *L)调用写 InitList(L)函数内部用 L-length 访问成员。调试时如果你发现调用后数据没变不要怀疑逻辑先检查形参是不是指针这一步能省掉大量排错时间。5.3 malloc 之后忘了 free内存泄漏不一定立刻报错现象是链表、二叉树代码跑的时候没问题但反复创建销毁结构体后程序内存占用越来越高最后在某次大批量插入时崩溃。原因是每次 malloc 一个新节点都是在堆上分配内存不调用 free 就不会归还系统内存被慢慢耗光。很多学生在实验课上只跑一次根本察觉不到问题直到加的测试次数变多才翻车。解决方法是建立“一个 malloc 配一个 free”的意识。写链表删除函数时删掉节点不要只改指针要先把节点从链上摘下来再 free写销毁函数时用遍历的方式一个一个释放而不是直接 free 头结点那样会漏掉后面所有节点。更谨慎的做法是在调试阶段用一个全局计数器记录 malloc 和 free 的次数程序结束前对比两个数是否相等。5.4 二叉树递归到死先检查终止条件再检查参数现象是递归中序遍历一棵小树一切正常换一棵深度较大的树后程序在某个节点反复进入同一层甚至栈溢出崩溃。原因是递归终止条件写错或落后。比如用 if (t-lchild ! NULL) 代替 if (t NULL) return那么在空树和叶子节点时就会出现对空指针子域的判断更常见的错误是递归调用传参时把左右孩子写反导致永远在同一棵子树里打转。解决方法是把“判空返回”作为递归函数第一行先建立终止条件再处理业务逻辑。写完后用一两层的二叉树验证根节点只有左孩子、只有右孩子、左右都为空这三种小数据能过这三组基本就说明递归结构没写错。5.5 书上代码用全局变量搬到自己工程里互相污染现象是把严蔚敏书里某个算法段落照抄下来里面的表或栈被定义成全局变量。同一个程序里跑两组测试时前一组的残值影响了后一组的运行结果。原因是教材为了描述简洁把一些工作变量放到了函数外环境变量一多函数之间的状态就纠缠在一起了。解决方法是把全局状态收进结构体例如把“当前栈顶 top”封装到栈结构体里将节点指针作为参数传入函数或者在每个测试函数里先调用 Init 函数重置状态。习惯上我写实验代码时会保证每个 main 只测一个场景测第二个场景前重新初始化所有数据结构而不是依赖代码里某个“反正上次已经清零了”的假设。6. 调试与验证给数据结构代码补一个可复测的测试骨架6.1 malloc 之后立刻初始化指针这是一个性价比极高的习惯。写链表或二叉树节点时malloc 成功后的第一件事不是赋值 data而是把 next 或 lchild 置为 NULL再填数据。这个顺序看着不起眼却能挡住一大类野指针崩溃。#include assert.h BiTNode *make_tree_node(ElemType v) { BiTNode *p (BiTNode *)malloc(sizeof(BiTNode)); assert(p ! NULL); /* 内存分配失败立刻暴露 */ p-data v; p-lchild NULL; /* 先置空再让调用方挂接 */ p-rchild NULL; return p; }6.2 跑通三组边界数据再提交我给自己的测试规则是任何数据结构代码都要先喂三组数据。空表或空树只有一个节点普通规模加退化形状比如链表退化成单节点、二叉树退化成一串右孩子。空数据能验证判空逻辑单节点能验证首尾操作退化形状能验证算法对极端深度的承受力。这三组跑完代码的架子基本就是稳的。6.3 用打印定位问题指针别瞎猜调试链表时与其盯着代码发呆不如遍历打印每个节点的地址和 next 地址看看哪里断链了。我以前写链表节点时总忘记初始化 next程序跑着跑着就飞到不知道哪里后来养成每个 malloc 后先置空指针的习惯这类问题基本绝迹了。希望帮到你。本文还有配套的精品资源点击获取
返回列表