☰
教学计划编制问题:C语言实现AOV网与拓扑排序全解析
2026/10/18 17:49:11 网站建设 项目流程

简介:这是一份面向C语言/C++课程设计的“教学计划编制问题”完整方案,适合高校学生完成程序设计实践或复习面向对象编程核心内容。压缩包共含7个文件,包括3个cpp源程序、2个头文件和2个doc文档,覆盖从语法基础到类与对象、模板与异常处理等知识点,doc文档则提供课程设计报告与说明书,便于对照项目目标与设计思路。资源包大小约893KB,已有1471人学习浏览。读者可从中获得可直接编译运行的源码框架、模块化代码组织方式以及规范的课程设计文档范例,既能用于理解教学计划编制问题的求解流程,也能参考其文件划分与注释风格,提升实际编程与文档撰写能力。

1. 教学计划编制问题:把先修关系变成可行课表的C语言课设

「教学计划编制问题」是C语言课程设计里出现率很高的一道题:给你一批课程名,再给一堆先修关系,比如没学《C语言程序设计》就不能选《数据结构》,要求排出一份满足所有先修条件的开课顺序。很多同学手推能推出来,一落到C语言代码就不知道从哪下手——其实底子就是数据结构里的AOV网和拓扑排序。

这份资源把整个工程做全了:用C语言实现课程读入、建图、拓扑排序、环检测和结果输出,数据文件配好,编译就能跑。适合三类人——正在做课程设计的学生,可以直接借鉴模块结构和代码实现;想复习图论与拓扑排序的开发者,能拿到一份完整可运行的参考实现;打算在课设基础上继续做学期分配、学分汇总的二次开发者,底层的邻接表和排序框架已经帮你搭好,改起来顺手。

2. 总体设计与数据结构:AOV网、邻接表和入度表怎么定

2.1 先修关系建模:把课表问题翻译成一张有向图

拿到题先别急着写码,先把问题翻译成图论语言。每门课程是一个顶点,一条有向边 A→B 表示「A 是 B 的先修课程」。以《C语言程序设计》→《数据结构》为例,《数据结构》必须排在《C语言程序设计》之后。整张图就是数据结构教材里的 AOV 网(Activity On Vertex Network),顶点代表活动,边代表活动之间的先后制约。

教学计划编制,本质就是在这个有向图上做一次拓扑排序,得到线性序列,使每条边 A→B 都满足 A 排在 B 前面。做这个转化,有两个点必须想清楚。

第一,有环就无解。如果依赖关系里出现《操作系统》依赖《数据结构》、《数据结构》又反过来依赖《操作系统》,那么不管怎么排都会违反其中一条先修约束。程序必须有环检测能力,不能跑成死循环或输出残缺序列。

第二,可行解不唯一。只要每门课都满足先修条件,任何一个序列都判对。所以验证程序时不能用「输出和标准答案一模一样」来校验,而要检查「每条先修边的起点是否都排在终点之前」,这是后面测试章节的立足点。

这段建模思路放之四海皆准:无论资源包里代码细节怎么变,图结构必然包含「顶点数组、每条边的方向、每个顶点的入度」这三件事。看懂这张图,就等于看懂了整份源码的骨架。

2.2 数据结构选型:为什么是邻接表和入度表,而不是邻接矩阵

课程先修关系是典型的稀疏图——一个学期十几门课,每门课的先修课普遍只有两三门。用邻接矩阵存 n 门课要 n×n 个单元,n 到 100 就有 1 万个格子,大部分填 0,遍历某门课的所有后继还要扫一整行;邻接表只存真实存在的边,并且天然「顺着链表就能拿到某顶点的全部出边」,正好命中拓扑排序最频繁的操作——找到顶点 v 的所有后继,把它们的入度各减一。

课程设计里两种写法都有人交,但我一般推荐邻接表,理由就一条:算法主循环的时间复杂度 O(n+e) 依赖「每条边只被访问一次」,邻接表能做到,邻接矩阵在边稀疏时做不到。下面是工程里最常见的结构体组合:

#define MAX_COURSE_NUM 100 // 课程数量上限 #define MAX_NAME_LEN 50 // 课程名字符串最大长度 typedef struct EdgeNode { int adjVertex; // 后继课程在顶点数组中的下标 struct EdgeNode *next; // 下一条出边 } EdgeNode; typedef struct VertexNode { char name[MAX_NAME_LEN]; // 课程名 int indegree; // 入度:还有几门先修课没排到 EdgeNode *firstEdge; // 出边链表头 } VertexNode; typedef struct { VertexNode vertices[MAX_COURSE_NUM]; // 顶点数组 int vertexNum; // 课程总数 int edgeNum; // 先修关系总数 } AOVGraph;

三个结构体各管一摊:EdgeNode 只负责描述「一条边指向哪个顶点」;VertexNode 把课程名、入度、出边链表头捆在一起,入度就是拓扑排序的核心判据;AOVGraph 是整张图的容器,顺带记录顶点数和边数。

入度表放在顶点结构体里而不是单独开数组,是刻意的设计:排序过程中每次取出一个顶点,就要对它的全部后继做入度减一,数据放在顶点旁边,访问链路最短。邻接表在 C 语言里基本都用头插法建边,新来的边挂在链表头,建图成本 O(1),代价是邻接表的边顺序和输入顺序相反,但拓扑排序不依赖边的遍历顺序,这个代价可以忽略。

2.3 模块划分与典型目录结构

课程设计项目最忌讳所有逻辑堆在一个 main 里,跑到三千行以后根本没法调。我一般习惯把工程拆成「图模块、排序模块、入口」三块,典型目录长这样:

文件职责
main.c入口:串起建图、排序、输出,打印提示信息
graph.h / graph.c图结构体定义、初始化、数据文件读入、课程名查下标
topo.h / topo.c拓扑排序主算法、环检测、结果数组管理
courses.txt输入数据:课程数、先修关系数、课程名列表、先修关系列表
课程设计报告需求分析、流程图、测试用例与结果截图

模块拆分的收益在接口上立刻体现出来,图模块和排序模块各暴露一组小函数,main 根本不关心底层数组怎么组织:

// graph.h int loadGraph(AOVGraph *g, const char *filePath); // 读文件建图,成功返回 0 int findVertex(AOVGraph *g, const char *name); // 课程名换下标,查不到返回 -1 // topo.h int topoSort(AOVGraph *g, int *result); // 结果写入数组,1 无环 0 有环

排序结果不直接打印,而是用 int 数组回传,这是个小但重要的设计决策。调用方拿到 result 后,想按先修顺序逐行打印也好,想每五门课换行也好,想进一步按学期分组也好,都不用改 topo 模块。后面二次开发章节的学期分批改造,就是靠这个接口接住的。资源包里若配了实验报告文档,流程图一般也按这三个模块分层画,对着源码读,思路会顺很多。

3. 核心实现拆解:拓扑排序从数据读入到课表输出的完整链路

3.1 数据读入与建图:初始化顶点、挂邻接边、累计入度

数据文件是程序的第一道关口。常见的输入格式是:第一行两个整数,课程总数 n 和先修关系总数 m;接着 n 行每行一个课程名;再接着 m 行,每行两个课程名,表示「前一个是后一个的先修」。读入建图的函数长这样:

int loadGraph(AOVGraph *g, const char *filePath) { FILE *fp = fopen(filePath, "r"); if (fp == NULL) { perror("打开数据文件失败"); return -1; } fscanf(fp, "%d %d", &g->vertexNum, &g->edgeNum); if (g->vertexNum > MAX_COURSE_NUM) { fprintf(stderr, "课程数超出上限 %d\n", MAX_COURSE_NUM); fclose(fp); return -1; } for (int i = 0; i < g->vertexNum; i++) { fscanf(fp, "%s", g->vertices[i].name); g->vertices[i].indegree = 0; // 入度先清零 g->vertices[i].firstEdge = NULL; // 出边链表头置空 } char pre[MAX_NAME_LEN], cur[MAX_NAME_LEN]; for (int j = 0; j < g->edgeNum; j++) { fscanf(fp, "%s %s", pre, cur); // pre 是 cur 的先修课程 int u = findVertex(g, pre); // 起点下标 int v = findVertex(g, cur); // 终点下标 if (u == -1 || v == -1) { fprintf(stderr, "未知课程名: %s -> %s\n", pre, cur); fclose(fp); return -1; } EdgeNode *node = (EdgeNode *)malloc(sizeof(EdgeNode)); node->adjVertex = v; // 建 u -> v 的边 node->next = g->vertices[u].firstEdge; g->vertices[u].firstEdge = node; // 头插法挂上链表 g->vertices[v].indegree++; // 终点入度加 1 } fclose(fp); return 0; }

代码分成三段看。第一段打开文件后先读顶点数和边数,顺手做一次 MAX_COURSE_NUM 越界检查——这是防御性编程里最便宜的一道保险,数据文件手滑写错数字时,程序在报错退出和数组越界之间会选择前者。第二段用循环初始化顶点:读课程名、入度清零、出边链表头置空。

第三段是建图核心。每条边先调 findVertex 把 pre 和 cur 从字符串换成顶点下标,换不到就报错退出,防止脏数据污染后续逻辑;然后申请一个 EdgeNode,adjVertex 存终点 v 的下标,头插到起点 u 的链表头上;最后把终点 v 的 indegree 加 1。入度加在终点上,语义就是「这门课又多了一位先修还没排到」。

findVertex 本身没有玄机,课程规模在 100 以内,线性扫描完全够:

int findVertex(AOVGraph *g, const char *name) { for (int i = 0; i < g->vertexNum; i++) { if (strcmp(g->vertices[i].name, name) == 0) { return i; } } return -1; }

线性查找每调一次 O(n),建图阶段一共调 2m 次,总量 O(n·m) 在几百门课的量级下毫无压力。真要撑到几千门课再考虑字符串哈希映射,课设阶段属于过度设计。

3.2 Kahn 拓扑排序:入度为 0 的课程进栈,逐个取出并削减后继入度

拓扑排序最经典的实现是 Kahn 算法,也叫入度归零法:反复「拿出一个当前入度为 0 的顶点,写进结果,再把它的所有后继课程的入度减一」,循环到没有入度为 0 的顶点可拿为止。不真正删边,只维护一个入度栈,实现干净利落:

int topoSort(AOVGraph *g, int *result) { int stack[MAX_COURSE_NUM]; int top = -1; int count = 0; // 第一轮扫描:所有入度为 0 的课程先进栈 for (int i = 0; i < g->vertexNum; i++) { if (g->vertices[i].indegree == 0) { stack[++top] = i; } } while (top != -1) { int v = stack[top--]; // 弹出一个可排课程 result[count++] = v; // 写入结果序列 EdgeNode *p = g->vertices[v].firstEdge; while (p != NULL) { int w = p->adjVertex; // 后继课程 g->vertices[w].indegree--; // 先修已排,入度减 1 if (g->vertices[w].indegree == 0) { stack[++top] = w; // 入度归零,可以排课了 } p = p->next; // 继续遍历下一条出边 } } return (count == g->vertexNum) ? 1 : 0; // 1 无环,0 有环 }

主循环的节奏是「弹顶点、写结果、遍历出边、减入度、判归零」。取出顶点 v 后,沿 v 的出边链表走到底,每个后继 w 的入度减一;哪个 w 的入度恰好减到 0,说明它全部先修课程都已排完,立刻入栈。每一条边在这个循环里只处理一次,每个顶点入栈出栈一次,整体复杂度 O(n+e)。

这里用栈还是用队列,结果序列会不一样:栈是后进先出,偏向「先到先取的顶点最后扩展」;队列是先入先出,更接近宽度优先。两种结果都满足先修约束,课设判题一般不锁唯一输出,选栈只是因为实现省事。想复现教材上的「从左到右平铺」效果,把 stack 换成循环队列即可。

注意:topoSort 会原地修改入度数组,排序结束后入度信息不再可用。如果程序还要基于入度做别的统计,得先把入度复制一份现场,这是避坑章节要展开的一个高频翻车点。

3.3 结果输出与环检测:count 对不上课程数,就是有环

main 函数把三个模块串成完整链路,同时负责把拓扑排序的下标结果还原成课程名:

int main(void) { AOVGraph g; int result[MAX_COURSE_NUM]; if (loadGraph(&g, "courses.txt") != 0) { return 1; // 建图失败,文件路径或数据格式有问题 } if (topoSort(&g, result)) { printf("可行的教学计划如下:\n"); for (int i = 0; i < g.vertexNum; i++) { printf("%d. %s\n", i + 1, g.vertices[result[i]].name); } } else { printf("错误:先修关系存在环,无法排出教学计划。\n"); // 调试时把剩余入度大于 0 的课程列出来,方便定位环 for (int i = 0; i < g.vertexNum; i++) { if (g.vertices[i].indegree > 0) { printf("环内课程嫌疑: %s (剩余入度 %d)\n", g.vertices[i].name, g.vertices[i].indegree); } } } return 0; }

环检测的判据是 topoSort 的返回值:返回 0,说明 while 循环中途栈就空了,还有课程入度恒大于 0,这些课必然处在环里,或者被环上的课程间接卡住。无环情况下,每门课最终都会入度为 0 被取走,count 一定等于 vertexNum。

有环时打印剩余入度大于 0 的课程,是定位环的土办法:虽然不能精确画出环的形状,但能圈出嫌疑范围,手排数据时对着这一小撮课检查先修关系,通常一眼就能找出循环依赖。让用户拿着「哪里出错、错在哪些课」的信息去改数据文件,比一句干巴巴的「有环」友好得多。

4. 编译运行与验证:三步跑通并确认课表真的合法

4.1 编译命令与数据文件准备

拿到资源包,先在源码目录打开终端,三个源文件要一起参与编译链接:

gcc -Wall -o plan main.c graph.c topo.c

-Wall 参数把编译器警告全部打开。初学者最容易犯的错就是不加 -Wall,把该在编译期暴露的问题拖到运行期。如果是在 IDE 里点按钮编译,注意工程要包含这三个 .c 文件,只把 main.c 加进工程会导致链接时报「未定义引用」的错误。

运行前先准备好数据文件 courses.txt,课程设计里最常见的样例长这样:

7 8 C语言程序设计 数据结构 离散数学 操作系统 计算机网络 编译原理 软件工程 C语言程序设计 数据结构 离散数学 数据结构 数据结构 操作系统 数据结构 编译原理 计算机网络 操作系统 操作系统 软件工程 编译原理 软件工程 离散数学 计算机网络

第一行 7 是课程数,8 是先修关系数;中间 7 行是课程名;最后 8 行是先修关系,每行「前课后课」。注意课程名里不要带空格,fscanf 的 %s 遇到空格就断词,课程名一旦写成「C语言 程序设计」这种带空格的形态,读进程序会变成两个单词,findVertex 必然查不到。这是数据文件约定,也是需要写进实验报告的约定。

编译没问题后直接跑 ./plan(Windows 下是 plan.exe),得到输出:

可行的教学计划如下: 1. C语言程序设计 2. 离散数学 3. 数据结构 4. 计算机网络 5. 操作系统 6. 编译原理 7. 软件工程

这个顺序只是无数可行解之一。《离散数学》和《C语言程序设计》互不依赖,谁先谁后都合法;《操作系统》之前有《数据结构》和《计算机网络》,顺序里也确实在它们后面。别拿这份输出当唯一标准答案去对别人的程序。

4.2 三组测试用例:正常、有环、单条链

验证程序不能只跑一组正常数据,至少补上三种形态。我把它们整理成一张表,照着逐项过:

用例输入要点期望行为
正常多分支7 课 8 关系输出全部 7 门课,checkOrder 返回 1
二元环添加互相先修关系输出「存在环」并列出嫌疑课
单条链A→B→C→D顺序唯一,正常结束不死循环
零依赖5 课 0 关系全部课程直接输出,不越界不崩溃

第一个正常用例用样例数据跑,肉眼核对每条先修边,再用 4.3 的 checkOrder 做机器校验。第二个有环用例,把样例里「操作系统 软件工程」和「软件工程 操作系统」同时保留,两者互相先修,程序应当输出「存在环」提示并列出剩余入度大于 0 的课程。这个用例专门逼环检测逻辑——很多实现把有环情况漏判成「只输出了部分课程」,不报错也不卡死,很难察觉,必须主动构造环来验证。

第三个极端形态分两类。单条链如 A→B→C→D,验证链表遍历时指针移动没问题——出边链表 while 循环里漏写 p = p->next,在普通数据上可能侥幸跑完,在长链上直接死循环。再测一组「课程数 5、先修关系数 0」,所有课程第一轮全部进栈,程序应正常输出且不越界,零依赖用例经常能抓到栈数组越界的隐藏 bug。

数据文件在 Windows 下编辑还有一个常见坑:记事本保存的是 CRLF 换行,课程名后面跟着回车符。fscanf 的 %s 默认会跳过空白字符,一般不会把 \r 扫进名字,但在一些老编译器或换行符处理不规范的实现里,课程名会带着 \r 尾巴,导致 strcmp 永远不等、findVertex 全部返回 -1。遇到这种情况,把数据文件统一转成 LF 换行,或者干脆用 C1、C2 这种 ASCII 课程名,一劳永逸。

提示:课程名不要带空格和中文,数据文件统一用 LF 换行,能省掉一大半环境类问题。

4.3 一个 checkOrder 函数,机器校验顺序合法性

前面说过拓扑排序结果不唯一,肉眼核对只能看小样例。我习惯在测试代码里加一个校验函数,检查「每条先修边 u→v,u 在结果数组中的位置必须小于 v」:

int checkOrder(AOVGraph *g, int *result) { int pos[MAX_COURSE_NUM]; for (int i = 0; i < g->vertexNum; i++) { pos[result[i]] = i; // 记录每门课在结果中的位次 } for (int i = 0; i < g->vertexNum; i++) { for (EdgeNode *p = g->vertices[i].firstEdge; p != NULL; p = p->next) { if (pos[i] > pos[p->adjVertex]) { return 0; // 先修排到了后面,排序非法 } } } return 1; }

checkOrder 的原理是先建一张「课程 → 位次」的反查表 pos,再遍历每条边。pos[i] 表示顶点 i 在 result 里的下标,若某条边 u→v 的起点位次大于终点位次,说明先修课排到了后头,直接判非法。时间复杂度同样是 O(n+e),放在 topoSort 之后调用,整条链路变成「建图 → 排序 → 校验」的闭环。

这个函数最大价值在回归测试:后面做学期分配改造、换数据结构、改输出方式,跑完先过一遍 checkOrder,任何把顺序改坏的回退都能第一时间被发现。我在二次开发章节还会继续用它做批量验证。

5. 避坑排查记录:五个高频问题从现象到解决

5.1 编译环节两个高频问题

问题一:编译报「隐式声明」或「未定义引用」。

现象:编译时提示 implicit declaration of function 'loadGraph',或者链接阶段报 undefined reference to 'loadGraph',但 graph.c 里明明写了这个函数。

原因:前者是 main.c 忘了包含 "graph.h",编译器看不到函数原型,只能用默认规则猜;后者是链接阶段缺少目标文件——只编译了 main.c,graph.c 和 topo.c 根本没参与编译,符号自然找不到。还有一种情况是头文件里的声明和源文件里的定义参数不一致,比如声明是 const char*,定义是 char*,链接同样失败。

解决:补上 #include "graph.h",并确认编译命令带全三个源文件。工程里文件一多,我习惯写个 Makefile 或脚本,把 gcc main.c graph.c topo.c -o plan 固化下来,省得每次手动敲,也避免漏文件这种低级失误。

问题二:课程名乱码或带着看不见的尾巴。

现象:中文课程名打印出来是乱码;或者输入文件里课程名明明一致,findVertex 却返回 -1,程序报「未知课程名」退出。

原因:一多半是字符编码问题。数据文件是 UTF-8 编码,而 Windows 控制台默认 GBK,中文显示成乱码,这不影响程序逻辑但影响人看结果;另一小半是换行符问题,某些环境下课程名被读进了 \r,字符串肉眼一样、字节不一样,strcmp 就判定不相等。

解决:编码上统一口径——要么数据文件和源代码全部 UTF-8,控制台执行 chcp 65001 切编码再运行;要么课程名直接用无中文、无空格的 ASCII 标识符,避开车轮战。数据文件换行统一转 LF,用编辑器右下角切换即可。这两条约定写进实验报告,能帮验收的人省掉一晚上的排查时间。

5.2 运行与结果三个高频问题

问题三:同一个程序里跑两次排序,第二次结果乱套。

现象:第一次 topoSort 输出正常,第二次再调 topoSort(比如换了批数据但复用同一个图对象),输出的课程数量变少,顺序也不对。

原因:Kahn 算法直接在 g->vertices[i].indegree 上减,第一次排序跑完,无环图的所有入度都变成了 0;第二次排序第一轮就把所有课程全部进栈,顺序退化成「出栈逆序」,入度信息彻底丢失。这个 bug 最阴的地方在于,很多课设程序只跑一次排序,根本暴露不出来。

解决:排序前先复制一份入度现场,排序全程操作副本:

int tempIn[MAX_COURSE_NUM]; for (int i = 0; i < g->vertexNum; i++) { tempIn[i] = g->vertices[i].indegree; } // 后续所有入度增减都改在 tempIn 上做,原图不受影响

这样图对象在一次排序之后仍保持建图时的样子,第二次调用结果不会偏移。从那以后我养成的习惯是:任何遍历类算法,只要会改原数据结构,要么复制现场,要么提供 reset 接口,别指望调用方记得重建。

问题四:输出顺序整体倒置,先修课排到了后面。

现象:程序能跑、不报错,但《数据结构》排在《C语言程序设计》前面,checkOrder 校验直接返回 0。

原因:八成是边建反了。读入时把「A 是 B 的先修」建成了 B→A 的边,拓扑排序结果自然整体反向。另一个低频原因是 findVertex 里 strcmp 的比较逻辑写反,把相等判成了不等,导致查无此课。

解决:先用两课一链的最小数据测:输入 C语言程序设计 数据结构,期望输出前者在前。确认建图处 u 取的是 pre、v 取的是 cur、indegree++ 加在 v 上。再把 checkOrder 写进测试流程,边方向错了立刻报「先修排到了后面」,不会拖到人工验收才发现。

问题五:手写递归 DFS 版拓扑排序,课程一多就爆栈。

现象:不用 Kahn 迭代写法,自己用递归 DFS 实现拓扑排序,数据量稍微大一点,程序直接崩溃或输出错乱。

原因:递归深度等于最长依赖链长度,课程多、依赖链深时容易耗尽调用栈;更隐蔽的是 DFS 拓扑排序需要区分「未访问 / 访问中 / 已访问」三种状态,少了「访问中」状态的染色,环检测和顺序输出都会出错,而且错误出现得很随机。

解决:课程设计这个规模,老老实实用 Kahn 的迭代写法。Kahn 不需要递归,显式栈深度可控,环检测天然融在 count 计数里。真要写 DFS,也务必用显式栈模拟递归并配合三色标记,别把调用栈当数据栈用。这个坑属于「学过算法但没实现过」的经典翻车现场,踩过的人应该都有同感。

6. 二次开发思路:从「能排课」到「排得合理」

6.1 三个可落地的改造方向

方向一:按学期分批输出。现在的输出是一条线性序列,但真实教学计划要落到多个学期,每学期课程数有上限。改造思路是把一次拓扑排序改成按层推进:先取出当前所有入度为 0 的课程作为第一学期,统一对它们的后继入度减一,再把新入度为 0 的课程作为第二学期,如此循环。这个改造只动 topoSort 的循环结构,图结构完全不用变:

int semester = 1; while (count < g->vertexNum) { // 当前入度为 0 的课收集到 currentSemester // 打印第 semester 学期的课程 // 对这些课的后继统一减入度 semester++; }

关键细节是「先收集、后削减」两步走,不能在削减过程中把新入度为 0 的课程混进本学期,否则学期分组就乱了。

方向二:加学分字段与学分汇总。在课程名后面跟一个整数表示学分,读入时存进 VertexNode 新增的 credit 成员;输出时按学期累加,顺手检查每学期学分是否超标。学分是顶点属性,改成在结构体里加一个成员即可,完全不动邻接链表,属于低成本高收益的课设加分项。

方向三:用关键路径估计总学期数。给每条先修边加权重表示周学时或持续周期,AOV 网升级成 AOE 网,求关键路径可以估算完成全部课程的最短总时长。这个方向比前两个深一截,能撑起课设的难度上限。但要记住:关键路径解决的是「总工期」而不是「合法顺序」,与拓扑排序是互补关系,别混为一谈。

6.2 改完怎么验证

改完代码别着急打包交差。我的习惯是走三步:先用第 4 章的三组测试用例回归,确认基础排序没被改坏;再写个小脚本随机生成无环课程关系(按拓扑序从小到大连边,天然无环),跑程序后用 checkOrder 批量校验,覆盖手写用例想不到的边角情况;最后跑一次有环用例,确认环检测依然报错。三步全绿,改造才算完成。

这套流程走顺以后,课设代码基本就稳了。想起当年我第一版拓扑排序直接把入度表改得面目全非,第二次排序就乱序,后来才养成「任何排序算法都先复制现场数据,再在副本上操作」的习惯。从那以后,我每次拿到课设源码,第一件事就是看它有没有破坏输入数据、有没有独立的校验函数——这两点看明白,代码能不能跑、好不好改,心里就有底了。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询