拓扑排序

 

一、问题描述

    在AOV网中为了更好地完成工程,必须满足活动之间先后关系,需要将各活动排一个先后次序即为拓扑排序。拓扑排序可以应用于教学计划的安排,根据课程之间的依赖关系,制定课程安排计划。按照用户输入的课程数,课程间的先后关系数目以及课程间两两间的先后关系,程序执行后会给出符合拓扑排序的课程安排计划。

 

二、基本要求

 

    1、选择合适的存储结构,建立有向无环图,并输出该图;

    2、实现拓扑排序算法;

    3、运用拓扑排序实现对教学计划安排的检验。

 

三、算法思想

 

    1、采用邻接表存储结构实现有向图;有向图需通过顶点数、弧数、顶点以及弧等信息建立。
    2、拓扑排序算法 void TopologicalSort(ALGraph G) 中,先输出入度为零的顶点,而后输出新的入度为零的顶点,此操作可利用栈或队列实现。考虑到教学计划安排的实际情况,一般先学基础课(入度为零),再学专业课(入度不为零),与队列先进先出的特点相符,故采用队列实现。
    3、拓扑排序算法void TopologicalSort(ALGraph G),大体思想为:
    1)遍历有向图各顶点的入度,将所有入度为零的顶点入队列;
    2)队列非空时,输出一个顶点,并对输出的顶点数计数;
    3)该顶点的所有邻接点入度减一,若减一后入度为零则入队列;
    4)重复2)、3),直到队列为空,若输出的顶点数与图的顶点数相等则该图可拓扑排序,否则图中有环。
    4、要对教学计划安排进行检验,因此编写了检测用户输入的课程序列是否是拓扑序列的算法void TopSortCheck(ALGraph G),大体思想为:
    1)用户输入待检测的课程序列,将其存入数组;
    2)检查课程序列下一个元素是否是图中的顶点(课程),是则执行3),否则输出“课程XX不存在”并跳出;
    3)判断该顶点的入度是否为零,是则执行4),否则输出“入度不为零”并跳出;
    4)该顶点的所有邻接点入度减一;
    5)重复2)、3)、4)直到课程序列中所有元素均被遍历,则该序列是拓扑序列,否则不是拓扑序列。

 

 

 

四、数据结构

    1、链式队列的存储类型为:

1
2
3
4
5
6
7
8
9
10
11
12
typedef  int  ElemType;
typedef  struct  QNode
{
     ElemType data;
     struct  QNode *next;
   }QNode,*QueuePtr;
                               
typedef  struct
{
     QueuePtr front;
     QueuePtr rear;
}LinkQueue;

    2、图的类型(邻接表存储结构)为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef  char  VertexType[20]; //顶点信息(名称)
typedef  struct  ArcNode //链表结点
   {
       int  vexpos; //该弧所指向的顶点在数组中的位置
     struct  ArcNode *next; //指向当前起点的下一条弧的指针
}ArcNode;
typedef  struct  VNode //头结点
   {
      VertexType name; //顶点信息(名称)
    int  indegree; //顶点入度
    ArcNode *firstarc; //指向当前顶点的第一条弧的指针
}VNode,AdjList[MAX_VERTEX_NUM];
typedef  struct
{
    AdjList vexhead; //邻接表头结点数组
    int  vexnum,arcnum; //图的顶点数和弧数
}ALGraph;

五、模块划分

1、链式队列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
1)  void  InitQueue(LinkQueue *Q)
         功能:初始化链式队列
         参数:*Q 待初始化的队列
2)  int  QueueEmpty(LinkQueue Q)
         功能:判断空队列
         参数:Q 待判断的队列
         返回值:队列为空返回 1;队列非空返回 0
3)  void  EnQueue(LinkQueue *Q, ElemType e)
         功能:元素入队列
         参数:*Q 待操作的队列;e 要入队列的元素
4)  void  DeQueue(LinkQueue *Q, ElemType *e)
         功能:元素出队列
         参数:*Q 待操作的队列;*e 记录出队列元素的变量

 

2、有向图(DAG)邻接表存储结构(ALG)的操作

 

1
2
3
4
5
6
7
8
9
10
11
1)  int  LocateVex(ALGraph G,VertexType v)
        功能:顶点在头结点数组中的定位
        参数:G 待操作的图;v 要在图中定位的顶点
        返回值:顶点存在则返回在头结点数组中的下标;否则返回图的顶点数
2)  int  CreateGraph(ALGraph *G)
        功能:建立图
        函数内包含了由用户输入顶点数、弧数、顶点以及弧的操作
        参数:*G 待操作的图
        返回值:图建立成功返回1;图建立失败返回0
        错误判断:包含顶点数、弧数是否正确的判断;
        包含用户输入的弧的顶点是否存在的判断

3) 

 

1
2
3
void  PrintGraph(ALGraph G)
        功能:输出有向图
        参数:G 待输出的图

4) 

 

1
2
3
4
5
6
int  CreateGraph2(ALGraph *G)
        功能:建立预置课程图(输出函数内预置课程信息,并自动建立有向图)
        参数:*G 待操作的图
        返回值:图建立成功返回1;图建立失败返回0
        错误判断:包含顶点数、弧数是否正确的判断
        包含弧的顶点是否存在的判断

5) 

 

1
2
3
void  PrintGraph2(ALGraph G)
        功能:输出预置课程图
        参数:G 待输出的图

 

3、拓扑排序及拓扑检测算法

1
2
3
4
5
6
7
8
9
10
1)  void  TopologicalSort(ALGraph G)
        功能:实现拓扑排序
        参数:G 待进行拓扑排序的图
        错误判断:包含有向图是否有环的判断
2)  void  TopSortCheck(ALGraph G)
        功能:运用拓扑排序的思想检测教学计划
        函数内包含了由用户输入待检测的课程序列的操作
        参数:G 待操作的图
        错误判断:包含用户输入的课程是否存在的判断
        包含不是拓扑序列的原因(该课程有多少个先决课程未学)

    

 

4、主函数

 

1
2
3
void  main()
功能:主函数
利用 while 语句和 switch 语句实现菜单化调用函数

 

 六、源程序

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# include  "stdlib.h"
# include  "stdio.h"
# include  "string.h"
/*******************************************/
/*           以下为链式队列操作            */
/*******************************************/
/* 定义链式队列类型 */
   typedef  int  ElemType;
typedef struct QNode
     ElemType  data;
     struct QNode *next;
}QNode,*QueuePtr;
                  
typedef struct
{
     QueuePtr front;
     QueuePtr rear;
}LinkQueue;
1
2
3
4
5
6
7
8
/* 1.初始化链式队列 */
   void  InitQueue(LinkQueue *Q)
   {
       Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
       if  (!(Q->front))
           exit( 0 );
        Q->front->next = NULL;
}
1
2
3
4
5
6
7
8
/* 2.判断空队列 */
int  QueueEmpty(LinkQueue Q)
{
     if (Q.front==Q.rear)
         return  1 ;
     else
         return  0 ;
}
1
2
3
4
5
6
7
8
9
10
11
/* 3.入队列 */
void  EnQueue(LinkQueue *Q, ElemType e)
{
         QueuePtr p;
         p = (QueuePtr)malloc(sizeof(QNode));
         if  (!p)
            exit( 0 );
         p->data = e; p->next = NULL;
         Q->rear->next = p;
         Q->rear = p;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 4.出队列 */
void  DeQueue(LinkQueue *Q, ElemType *e)
{
     QueuePtr p;
     if (Q->front != Q->rear)
     {
         p = Q->front->next;
         *e = p->data;
         Q->front->next = p->next;
         if  (Q->rear == p)
              Q->rear = Q->front;
         free(p);
     }
}
1
2
3
/****************************************************/
/*    以下为有向图(DAG)邻接表存储结构(ALG)的操作    */
/****************************************************/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define MAX_VERTEX_NUM  20  //最大顶点个数
typedef char VertexType[ 20 ];  //顶点信息(名称)
   /* 图的类型定义(邻接表存储结构) */
            
typedef struct ArcNode  //链表结点
{
     int  vexpos;  //该弧所指向的顶点在数组中的位置
     struct ArcNode *next;  //指向当前起点的下一条弧的指针
   }ArcNode;
            
typedef struct VNode  //头结点
{
     VertexType name;  //顶点信息(名称)
     int  indegree;  //顶点入度
     ArcNode *firstarc;  //指向当前顶点的第一条弧的指针
   }VNode,AdjList[MAX_VERTEX_NUM];
            
typedef struct
{
     AdjList vexhead;  //邻接表头结点数组
     int  vexnum,arcnum;  //图的顶点数和弧数
}ALGraph;
1
2
3
4
5
6
7
8
9
/* 5.顶点在头结点数组中的定位 */
int  LocateVex(ALGraph G,VertexType v)
{
     int  i;
     for (i= 0 ;i<G.vexnum;i++)
         if (strcmp(v,G.vexhead[i].name) ==  0 )
             break ;
     return  i;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* 6.建立图(邻接表) */
int  CreateGraph(ALGraph *G)  //成功建立返回1,不成功则返回0
{
     int  i,j,k; VertexType v1,v2;ArcNode *newarc;
     printf( "\n输入有向图顶点数和弧数vexnum,arcnum:" );  //输入顶点数和弧数
     scanf( "%d,%d" ,&(*G).vexnum,&(*G).arcnum);  //输入并判断顶点数和弧数是否正确
     if ((*G).vexnum< 0 ||(*G).arcnum< 0 ||(*G).arcnum>(*G).vexnum*((*G).vexnum- 1 ))
     {
         printf( "\n顶点数或弧数不正确,有向图建立失败!\n" );
         return  0 ;
     }
     printf( "\n输入 %d 个顶点:" ,(*G).vexnum);  //输入顶点名称
     for (i= 0 ;i<(*G).vexnum;i++)
     {
         scanf( "%s" ,(*G).vexhead[i].name);
     }   
     printf( "\n顶点列表:\n共有%d个顶点:   " ,(*G).vexnum); //输出顶点名称
     for (i= 0 ;i<(*G).vexnum;i++)
         printf( "%s   " ,(*G).vexhead[i].name);
     for (i= 0 ;i<(*G).vexnum;i++)  //邻接表初始化
     {
         (*G).vexhead[i].firstarc=NULL;
         (*G).vexhead[i].indegree= 0 ;
     }
     printf( "\n\n输入 %d 条边:vi vj\n" ,(*G).arcnum);  //输入有向图的边
     for (k= 0 ;k<(*G).arcnum;k++)
     {
         scanf( "%s%s" ,v1,v2);  //v1是弧的起点(先决条件),v2是弧的终点
         i=LocateVex(*G,v1);j=LocateVex(*G,v2);  //定位顶点并判断顶点是否存在
         if (i>=(*G).vexnum)
         {
             printf( "顶点%s不存在,有向图建立失败!\n" ,v1);
             return  0 ;
         }
         if (j>=(*G).vexnum)
         {
             printf( "顶点%s不存在,有向图建立失败!\n" ,v2);
             return  0 ;
         }
         newarc=(ArcNode*)malloc(sizeof(ArcNode));  //前插法建顶点链表
         newarc->vexpos=j;
         if ((*G).vexhead[i].firstarc==NULL)
         {
             newarc->next=NULL;
             (*G).vexhead[i].firstarc=newarc;
         }
         else
         {
             newarc->next=(*G).vexhead[i].firstarc->next;
             (*G).vexhead[i].firstarc->next=newarc;
         }
         (*G).vexhead[j].indegree++;  //对应顶点入度计数加1
     }
     printf( "\n有向图建立成功!\n" );
     return  1 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 7.按邻接表方式输出有向图 */
void  PrintGraph(ALGraph G)
{
     int  i;ArcNode *p;
     printf( "\n输出有向图:\n" );
     for (i= 0 ; i<G.vexnum; i++)
     {
         printf( "\n顶点:%s   " ,G.vexhead[i].name);
         printf( "入度:%3d\n" ,G.vexhead[i].indegree);
         p=G.vexhead[i].firstarc;
         printf( "邻接点:" );
         while (p!=NULL)
         {
             printf( "%s   " ,G.vexhead[p->vexpos].name);
             p=p->next;
         }
         printf( "\n" );
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//为避免演示时要输入过多数据,以下函数将课程编号、课程间的先后关系通过数组预置
/* 8.建立预置课程图(邻接表) */
int  CreateGraph2(ALGraph *G)  //成功建立返回1,不成功则返回0
{
     int  i,j,k;VertexType v1,v2;ArcNode *newarc;
     VertexTypeSubjectName[ 12 ]={ "C1" , "C2" , "C3" , "C4" //课程名称
                                                 "C5" , "C6" , "C7" , "C8" ,
                                                 "C9" , "C10" , "C11" , "C12"  },
     RelationV1[ 16 ]={ "C1" , "C1" , "C2" , "C1" //基础课
                             "C3" , "C4" , "C11" , "C5" ,
                             "C3" , "C3" , "C6" , "C9" ,
                             "C9" , "C9" , "C10" , "C11" },
     RelationV2[ 16 ]={ "C2" , "C3" , "C3" , "C4" //以上面课程为基础的课
                             "C5" , "C5" , "C6" , "C7" ,
                             "C7" , "C8" , "C8" , "C10" ,
                             "C11" , "C12" , "C12" , "C12" ,};
     /* 输出本程序使用的课程及先后关系表 */
     printf( "\n本程序预置了如下课程及先后关系:\n" );
     printf("\n课程编号   课程名称          先决条件\n\
       C1      程序设计基础          无\n\
       C2      离散数学              C1\n\
       C3      数据结构              C1,C2\n\
       C4      汇编语言              C1\n\
       C5      语言的设计和分析      C3,C4\n\
       C6      计算机原理            C11\n\
       C7      编译原理              C5,C3\n\
       C8      操作系统              C3,C6\n\
       C9      高等数学              无\n\
       C10     线性代数              C9\n\
       C11     普通物理              C9\n\
       C12     数值分析            C9,C10,C1\n");
       system( "PAUSE" );
       (*G).vexnum= 12 ;(*G).arcnum= 16 ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
if ((*G).vexnum< 0 ||(*G).arcnum< 0 ||(*G).arcnum>(*G).vexnum*((*G).vexnum- 1 ))
{
     printf( "\n课程数或先后关系不正确,有向图建立失败!\n" );
     return  0 ;
//判断课程数和弧数是否正确
for (i= 0 ;i<(*G).vexnum;i++)
{
     strcpy((*G).vexhead[i].name,SubjectName[i]);
}
for (i= 0 ;i<(*G).vexnum;i++)  //邻接表初始化
{
     (*G).vexhead[i].firstarc=NULL;
     (*G).vexhead[i].indegree= 0 ;
}
for (k= 0 ;k<(*G).arcnum;k++)
{
     strcpy(v1,RelationV1[k]);strcpy(v2,RelationV2[k]);
     i=LocateVex(*G,v1);j=LocateVex(*G,v2);  //定位课程并判断课程是否存在
     if (i>=(*G).vexnum)
     {
         printf( "课程%s不存在,有向图建立失败!\n" ,v1); return  0 ; }
         if (j>=(*G).vexnum)
         {
             printf( "课程%s不存在,有向图建立失败!\n" ,v2);
             return  0 ;
         }
         newarc=(ArcNode*)malloc(sizeof(ArcNode));  //前插法建课程链表
         newarc->vexpos=j;
         if ((*G).vexhead[i].firstarc==NULL)
         {
             newarc->next=NULL;
             (*G).vexhead[i].firstarc=newarc;
         }
     else
     {
         newarc->next=(*G).vexhead[i].firstarc->next;
         (*G).vexhead[i].firstarc->next=newarc;
     }
     (*G).vexhead[j].indegree++;  //对应课程入度计数加1
}
printf( "\n有向图建立成功!\n" );
return  1 ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 9.按邻接表方式输出预置课程图 */
void  PrintGraph2(ALGraph G)
{
     int  i;ArcNode *p;
     printf( "\n输出有向图:\n" );
     for (i= 0 ; i<G.vexnum; i++)
     {
         printf( "\n课程:%s   " ,G.vexhead[i].name);
         printf( "入度:%3d\n" ,G.vexhead[i].indegree);
         p=G.vexhead[i].firstarc;
         printf( "以本课程为基础的课程:" );
         while (p!=NULL)
         {
             printf( "%s   " ,G.vexhead[p->vexpos].name);
             p=p->next;
         }
         printf( "\n" );
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**********************************/
/*       以下为拓扑排序算法       */
/**********************************/
/* 10.拓扑排序 */
void  TopologicalSort(ALGraph G)
{
     int  i,k,count;ElemType e;ArcNode *p;
     LinkQueue Q;  /*定义队列*/
     InitQueue(&Q);
     for (i= 0 ; i<G.vexnum; i++)  //零入度课程入队列
     if (!G.vexhead[i].indegree)
         EnQueue(&Q,i);
     count= 0 //对输出课程计数变量初始化
     printf( "\n\n\n以上课程的一个拓扑排序序列为:\n" );
     while (!QueueEmpty(Q))
     {
         DeQueue(&Q,&e);  //先将入度为零的课程输出
         printf( "%s   " ,G.vexhead[e].name);
         count++;  //对输出的顶点计数
         for (p=G.vexhead[e].firstarc;p;p=p->next)  //遍历当前课程的邻接点
         {
             k=p->vexpos;  //邻接点位置
             if (!(--G.vexhead[k].indegree))  //每个邻接点入度减1后若为零则入队列
             EnQueue(&Q,k);
         }
     }
     printf( "\n" );
     if (count<G.vexnum)
     {
         printf( "\n该有向图有回路,无法完成拓扑排序!\n" );
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**********************************/
/*       以下为拓扑检测算法       */
/**********************************/
/* 11.运用拓扑排序的思想检测教学计划 */
void  TopSortCheck(ALGraph G)
{
     int  i,k; ArcNode *p; VertexType v,CheckList[ 12 ]; //待检测序列
     TopologicalSort(G);
     printf( "\n输入待检测的课程序列:\n" );
     for (i= 0 ; i<G.vexnum; i++)  //输入待检测序列
         scanf( "%s" ,CheckList[i]);
     for (i= 0 ; i<G.vexnum; i++)
     {
         strcpy(v,CheckList[i]);
         k=LocateVex(G,v);
         if (k>=G.vexnum)  //判断课程是否存在
         {
             printf( "课程%s不存在!\n" ,v); return ;
         }
         if (G.vexhead[k].indegree!= 0 //判断课程入度是否为零
         {
             printf( "学习课程%s时,还有%d门先决课程未学!\n" ,v,G.vexhead[k].indegree);
             printf( "本课程序列不是拓扑序列\n\n" ); return ;
         }
         else
         {
             for (p=G.vexhead[LocateVex(G,v)].firstarc;p;p=p->next) //遍历此课程邻接点
             {
                 k=p->vexpos;  //邻接点位置
                 G.vexhead[k].indegree--;
             //每个邻接点入度减1
       }
    }
    printf( "本课程序列符合拓扑序列\n\n" );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*******************************************/
/*               12.主函数                 */
/*******************************************/
void  main()
{
     ALGraph G; int  menu,menu2;
     while ( 1 )
     {
         printf( "\n**********************************************\n" );
         printf( "*1.建立有向图并输出                   *\n" );
         printf( "*2.建立有向图并求一个拓扑排序序列     *\n" );
         printf( "*3.检测用户输入的课程安排             *\n" );
         printf( "*4.清屏                               *\n" );
         printf( "*5.退出                               *\n" );
         printf( "**********************************************\n" );
         printf( "请输入你的选择:" );
         scanf( "%d" ,&menu);
         switch (menu)
         {
          case  1 :
             if (CreateGraph(&G))
             {
                 system( "PAUSE" );
                 PrintGraph(G);
             } //有向图建成则执操作
             break ;
         case  2 :
             if (CreateGraph(&G))  //有向图建成则执操作
             {
                 system( "PAUSE" );
                 PrintGraph(G);
                 system( "PAUSE" );
                 TopologicalSort(G);
             }
             break ;
         case  3 :
             if (CreateGraph2(&G))
             {                              //有向图建成则执操作
                 system( "PAUSE" );
                 PrintGraph2(G); system( "PAUSE" );
                 while ( 1 )
                 {
                     TopSortCheck(G);
                     printf( "\n************************************\n" );
                     printf( "*1.检测其他课程序列         *\n" );
                     printf( "*2.完成检测                 *\n" );
                     printf( "************************************\n" );
                     printf( "请输入你的选择:" );
                     scanf( "%d" ,&menu2);
                     if (menu2!= 1 break ;
                 }
             }
             break ;
         case  4 :
             system( "CLS" );
             break ;
         case  5 :
             return ;
        }
     }
}

 


七、测试数据

    1、对“建立有向图并输出”的测试

    1) 正确的有向图信息

    顶点数和弧数:4,3

    顶点:A B C D

    边: A BB CC D

    2) 错误的边

    顶点数和弧数:4,3

    顶点:A B C D

    边: A BB CC E

    3) 错误的顶点数或弧数

    顶点数和弧数:3,7

    2、对“建立有向图并求一个拓扑排序序列”的测试

    1) 有向图能实现拓扑排序

    顶点数和弧数:5,5

    顶点:A B C D E

    边:A DD CC BE AE C

    2) 有向图不能实现拓扑排序

    顶点数和弧数:5,5

    顶点:A B C D E

    边:A DD CC BE AB A

    3、对“检测用户输入的课程安排”的测试

    1) 课程序列符合拓扑序列

    课程序列:C9 C10 C11 C6 C1 C12 C4 C2 C3 C5 C7 C8

    2) 课程序列中有课程不存在

    课程序列:C9 C10 C11 C6 C1 C12 C4 C2 C13 C5 C7 C8

    3) 课程序列不是拓扑序列

    课程序列:C9 C10 C11 C1 C8 C6 C12 C4 C2 C3 C5 C7

 

 

转载于:https://www.cnblogs.com/kai9655/archive/2013/05/04/3058595.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值