[问题描述]
大学的每个专业都要制定教学计划。假设任何专业都有固定的学习年限,每学年含两学期,每学期的时间长度和学分上限值均相等,每个专业开设的课程都是确定的,而且课程在开设时间的安排必须满足先修关系。每门课程有哪些先修课程是确定的,可以有任意多门,也可以没有。每门课恰好占一个学期。试在这样的前提下设计一个教学计划编制程序。
[基本要求]
(1)输入参数包括:学期总数,一学期的学分上限,每门课的课程号(固定占3位的字母数字串)、学分和直接先修课的课程号。
(2)允许用户指定下列两种编排策略之一:一是使学生在各学期中的学习负担尽量均匀;二是使课程尽可能地集中在前几个学期中。
(3)若根据给定的条件问题无解,则报告适当的信息;否则将教学计划输出到用户指定的文件中。计划的表格格式自行设计。
[测试数据]
学期总数:6;学分上限:10;该专业共开设12门课,课程号从C01到C12,学分顺序为2,3,4,3,2,3,4,4,7,5,2,3。先修关系如下:
课程编号 课程名称 先决条件
C1 程序设计基础 无
C2 离散数学 C1
C3 数据结构 C1,C2
C4 汇编语言 C1
C5 语言的设计和分析 C3,C4
C6 计算机原理 C11
C7 编译原理 C5,C3
C8 操作系统 C3,C6
C9 高等数学 无
C10 线性代数 C9
C11 普通物理 C9
C12 数值分析 C9,C10,C1
[实现提示]
可设学期总数不超过12,课程总数不超过100。如果输入的先修课程号不在该专业开设的课程序列中,则作为错误处理。应建立内部课程序号与课程号之间的对应关系。
为了调试方便,从文件中读取信息,其中文件地址在宏定义,文件内容如图(切记最后一行要有回车!)
注意图中光标位置(最后一行要有回车)
文件内容如下:
6 10 12
C1 2
C2 3 C1
C3 4 C1 C2
C4 3 C1
C5 2 C3 C4
C6 3 C11
C7 4 C5 C3
C8 4 C3 C6
C9 7
C10 5 C9
C11 2 C9
C12 3 C9 C10 C1
代码如下:
/*
实现内容:
教学计划编制问题(图的拓扑排序)
VS2019 编译通过
2020.8.2 王大花
*/
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#define VexDateType char
#define StackElemType int
#define MaxVexDateLength 3
#define MaxClassNum 100
#define MaxSemesterNum 12
#define TextPath "C:\\Users\\86132\\Desktop\\拓扑排序\\拓扑排序\\ALGraph.txt"
#define TeachPlanPath "C:\\Users\\86132\\Desktop\\拓扑排序\\拓扑排序\\TeachingPlan.txt"
//邻接表表示法
//弧结点
typedef struct _ARCNODE ArcNode;
struct _ARCNODE {
int AdjVex;
ArcNode* Next;
};
//顶点结点
typedef struct _VEXNODE {
VexDateType Date[MaxVexDateLength + 1];
int Credit;
ArcNode* FirstArc;
}VexNode;
//学期信息
typedef struct _INFO {
int SemesterNum;
int MaxCredit;
}Info;
//图
typedef struct _ALGRAPH {
VexNode* Vertics;
int VexNum;
int ArcNum;
int* InDegree;
Info* ExtraInfo;
}ALGraph;
//栈辅助拓扑排序(pos便于后续输出课程)
typedef struct _STACK {
StackElemType* Vertics;
int tail, pos;
}Stack;
void InitStack(Stack* S) {
S->Vertics = (StackElemType*)malloc(MaxClassNum * sizeof(StackElemType));
S->pos = S->tail = 0;
}
//根据元素寻找顶点位置 默认元素为C1C2C3……顺序排布
int Locate(char* ch) {
return (2 == strlen(ch)) ? ch[1] - '1' : (ch[1] - '0') * 10 + ch[2] - '1';
}
//从文件读取信息
void Creat_ALGraph(ALGraph* G) {
//初始化指针
G->ExtraInfo = (Info*)malloc(sizeof(Info));
//从文件读取信息
FILE* fp = fopen(TextPath, "r");
if (NULL == fp) {
printf("文件路径有误");
exit(EXIT_FAILURE);
}
//读学期总数 学分上限 课程总数(顶点数量)
fscanf(fp, "%d%d%d", &G->ExtraInfo->SemesterNum, &G->ExtraInfo->MaxCredit, &G->VexNum);
G->ArcNum = 0;
if (G->VexNum > MaxClassNum) {
printf("超出最大课程数%d,请更改数据\n", MaxClassNum);
exit(EXIT_FAILURE);
}
if (G->ExtraInfo->SemesterNum > 12) {
printf("超出最大学期数%d,请更改数据\n", MaxSemesterNum);
exit(EXIT_FAILURE);
}
G->Vertics = (VexNode*)malloc(sizeof(VexNode) * G->VexNum);
for (int i = 0; i < G->VexNum; i++)
G->Vertics[i].FirstArc = NULL;
//读取课程信息
for (int i = 0; i < G->VexNum; i++) {
//读取课程名称和学分
fscanf(fp, "%s%d", G->Vertics[i].Date, &G->Vertics[i].Credit);
//根据前序课程建立弧节点
while ('\n' != fgetc(fp)) {
char str[4];
int pos;
fscanf(fp, "%s", str);
pos = Locate(str);
//判断课程是不是没有
if (pos < 0 || pos > G->VexNum) {
printf("%s请输入正确的先决条件!\n", G->Vertics[i].Date);
exit(EXIT_FAILURE);
}
//更新弧节点 注意是pos指向i的弧
ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
p->AdjVex = i;
p->Next = G->Vertics[pos].FirstArc;
G->Vertics[pos].FirstArc = p;
G->ArcNum++;
}
}
fclose(fp);
//更新入度数组
G->InDegree = (int*)malloc(sizeof(int) * G->VexNum);
memset(G->InDegree, 0, sizeof(int) * G->VexNum);
for (int i = 0; i < G->VexNum; i++) {
for (ArcNode* p = G->Vertics[i].FirstArc; NULL != p; p = p->Next) {
G->InDegree[p->AdjVex]++;
}
}
}
//各学期负担集中
void Method1(ALGraph G, Stack S) {
int TotalCreadit = 0;
for (int i = 0; i < G.VexNum; i++)
TotalCreadit += G.Vertics[i].Credit;
int EachSemCredit = TotalCreadit / G.ExtraInfo->SemesterNum;
FILE* fp = fopen(TeachPlanPath, "w");
for (int i = 0; i < G.ExtraInfo->SemesterNum; i++) {
int tmp = 0;
printf("\n第%d个学期的课程:", i + 1);
fprintf(fp, "\n第%d个学期的课程:", i + 1);
do {
printf("%s ", G.Vertics[S.Vertics[S.pos]].Date);
fprintf(fp, "%s ", G.Vertics[S.Vertics[S.pos]].Date);
tmp += G.Vertics[S.Vertics[S.pos++]].Credit;
} while (tmp < EachSemCredit && S.pos < G.VexNum);
}
fclose(fp);
}
//集中在前几个学期
void Method2(ALGraph G) {
FILE* fp = fopen(TeachPlanPath, "w");
//重新更新入度数组
memset(G.InDegree, 0, sizeof(int) * G.VexNum);
for (int i = 0; i < G.VexNum; i++) {
for (ArcNode* p = G.Vertics[i].FirstArc; NULL != p; p = p->Next) {
G.InDegree[p->AdjVex]++;
}
}
int count = 0;
//记忆哪些课程是上学期学的
int* Mark = (int*)malloc(sizeof(int) * G.VexNum);
//外层循环为学期数
for (int i = 0; i < G.ExtraInfo->SemesterNum; i++) {
printf("\n第%d个学期的课程:", i + 1);
fprintf(fp, "\n第%d个学期的课程:", i + 1);
memset(Mark, 0, sizeof(int) * G.VexNum);
//扫描一遍入度结点把所有为0的输出
for (int j = 0; j < G.VexNum; j++) {
if (0 == G.InDegree[j]) {
count++;
Mark[j] = 1;
G.InDegree[j]--;
printf("%s ", G.Vertics[j].Date);
fprintf(fp, "%s ", G.Vertics[j].Date);
}
}
if (G.VexNum == count)
break;
//扫描Mark数组 通过标记为1的位置来更新入度结点
for (int j = 0; j < G.VexNum; j++) {
if (Mark[j]) {
for (ArcNode* p = G.Vertics[j].FirstArc; NULL != p; p = p->Next)
G.InDegree[p->AdjVex]--;
}
}
}
fclose(fp);
}
void TopologicalSort(ALGraph G) {
Stack S, OutCome;
InitStack(&S);
InitStack(&OutCome);
//寻找入读为0的顶点入栈
for (int i = 0; i < G.VexNum; i++) {
if (!G.InDegree[i]) {
S.Vertics[S.tail++] = i;
}
}
while (0 < S.tail) {
int tmp = OutCome.Vertics[OutCome.tail++] = S.Vertics[--S.tail];
for (ArcNode* p = G.Vertics[tmp].FirstArc; NULL != p; p = p->Next) {
G.InDegree[p->AdjVex]--;
if (0 == G.InDegree[p->AdjVex])
S.Vertics[S.tail++] = p->AdjVex;
}
}
if (OutCome.tail != G.VexNum) {
printf("课程的先决条件有误!\n");
exit(EXIT_FAILURE);
}
printf("请输入分配课程要求(各学期负担集中:1 尽可能集中前几个学期:2):");
int Command;
scanf("%d", &Command);
switch (Command) {
case 1: Method1(G, OutCome); break;
case 2: Method2(G); break;
default: {
printf("命令错误!");
exit(EXIT_FAILURE);
}
}
}
int main(void) {
ALGraph G;
Creat_ALGraph(&G);
TopologicalSort(G);
return 0;
}