拓扑排序问题
大学里,学生需要修完培养计划中的所有课程,才能毕业取得学位。例如计算机专业的学生要学习一系列课程,其中有些课程必须在其先修课程完成后才能学习,具体教学计划如下所示。
课程编号 课程名称 先修课程
假设每门课程的学习时间为一学期,试为计算机专业的学生设计课程的学习计划,使他们能在最短的时间内修完这些课程。设计算法求出每个学期的课程安排。
(1)用顶点表示课程,弧表示先决条件,则课程关系可用一有向无环图表示。
(2)基于邻接表存储结构实现。
(3)输出每学期的课程安排。
(4)在主函数中调用菜单函数调试程序。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 20//最大顶点个数
typedef struct VertexType //课程信息
{
int index; //课程的编号
char name[32]; //课程名
} VertexType;
typedef enum {
false, true
} bool;
typedef struct ArcNode {
int adjvex; //邻接点在数组中的位置下标
struct ArcNode * nextarc; //指向下一个邻接点的指针
} ArcNode;
typedef struct VNode {
VertexType data; //顶点的数据域
ArcNode * firstarc; //指向邻接点的指针
} VNode, AdjList[MAX_VERTEX_NUM]; //存储各链表头结点的数组
typedef struct {
AdjList vertices; //图中顶点及各邻接点数组
int vexnum, arcnum; //记录图中顶点数和边或弧数
} ALGraph;
int menu_select() {
int sn;
printf("-----------拓扑排序问题----------------------\n");
printf("1构建图\n");
printf("2输出拓扑排序\n");
printf("3退出\n");
printf(" 请选择1--3: ");
for (;;) //菜单功能选择
{
scanf("%d", &sn);
getchar();
if (sn < 1 || sn > 3)
printf("\n\t 输入选择错误,请重新选择 1--3: ");
else
break;
}
return sn;
}
/*TODO: 创建AOV网
功能描述: 创建AOV网,通过scanf("%d,%d", 节点数, 弧数);scanf("%s", 课程名);来创建节点。节点index从0开始递增
通过输入scanf("%d,%d", 弧的开始节点下标, 弧的结束节点下标)来创建弧
参数说明:G-ALGraph型指针参数
返回值说明:无
*/
void CreateAOV(ALGraph **G) {
*G = (ALGraph*) malloc(sizeof(ALGraph));
scanf("%d,%d", &((*G)->vexnum), &((*G)->arcnum));
for (int i = 0; i < (*G)->vexnum; i++) {
scanf("%s", (*G)->vertices[i].data.name);
(*G)->vertices[i].data.index = i;
(*G)->vertices[i].firstarc = NULL;
}
int initial, end;
for (int i = 0; i < (*G)->arcnum; i++) {
scanf("%d,%d", &initial, &end);
ArcNode *p = (ArcNode*) malloc(sizeof(ArcNode));
p->adjvex = end;
p->nextarc = NULL;
p->nextarc = (*G)->vertices[initial].firstarc;
(*G)->vertices[initial].firstarc = p;
}
}
//结构体定义栈结构
typedef struct stack {
VertexType data;
struct stack * next;
} stack;
//初始化栈结构
void initStack(stack* *S) {
(*S) = (stack*) malloc(sizeof(stack));
(*S)->next = NULL;
}
//判断链表是否为空
bool StackEmpty(stack S) {
if (S.next == NULL) {
return true;
}
return false;
}
//进栈,以头插法将新结点插入到链表中
void push(stack *S, VertexType u) {
stack *p = (stack*) malloc(sizeof(stack));
p->data = u;
p->next = NULL;
p->next = S->next;
S->next = p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量i;
void pop(stack *S, VertexType *i) {
stack *p = S->next;
*i = p->data;
S->next = S->next->next;
free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G, int indegree[]) {
//初始化数组,默认初始值全部为0
for (int i = 0; i < G.vexnum; i++) {
indegree[i] = 0;
}
//遍历邻接表,根据各链表中结点的数据域存储的各顶点位置下标,在indegree数组相应位置+1
for (int i = 0; i < G.vexnum; i++) {
ArcNode *p = G.vertices[i].firstarc;
while (p) {
indegree[p->adjvex]++;
p = p->nextarc;
}
}
}
/*TODO: 拓扑排序
功能描述:通过调用FindInDegree计算各顶点的入度并存入本地变量数组,顺序查找度为0的顶点,作为起始点,通过栈操作完成拓扑排序,打印节点printf("%s ", 课程名);
如果该图有回路则打印printf("该图有回路");
参数说明:G-ALGraph型参数
返回值说明:无
*/
void TopologicalSort(ALGraph G) {
int indegree[G.vexnum];
FindInDegree(G, indegree);
stack *S;
initStack(&S);
for (int i = 0; i < G.vexnum; i++) {
if (!indegree[i]) {
push(S, G.vertices[i].data);
}
}
int count = 0;
while (!StackEmpty(*S)) {
VertexType data;
pop(S, &data);
printf("%s ", data.name);
count++;
for (ArcNode *p = G.vertices[data.index].firstarc; p; p = p->nextarc) {
int k = p->adjvex;
if (!(--indegree[k])) {
push(S,G.vertices[k].data);
}
}
}
if (count < G.vexnum) {
printf("该图有回路");
return;
}
}
void main() {
ALGraph *G;
for (;;) {
switch (menu_select()) {
case 1:
CreateAOV(&G);
printf("\n");
break;
case 2:
printf("拓扑排序:");
TopologicalSort(*G);
printf("\n");
break;
case 3:
printf(" 再见!\n");
return;
} // switch语句结束
} // for循环结束
} // main()函数结束