拓扑排序

背景

  对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。 

一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。

问题描述

 

 

       假定一个计算机专业的学生必须完成如图3-4所列出的全部课程。在这里,课程代表活动,学习一门课程就表示进行一项活动,学习每门课程的先决条件是学完它的全部先修课程。如学习《数据结构》课程就必须安排在学完它的两门先修课程《离散数学》和《算法语言》之后。学习《高等数学》课程则可以随时安排,因为它是基础课程,没有先修课。问题:求出一种可行的课程安排顺序。

基本要求

(1)       输入n(课程数量),m(限制条件个数,即一共所需的先修课程数量);

(2)       输入m行限制条件,每行两个数字a b,分别表示学习课程 前者的先修课程;

(3)       输出一个合法的课程排序方案。

测试数据

  输入

              9 11

              3 1

              3 2

              4 3

              4 5

              5 2

              6 4

              6 5

              7 4

              7 9

              8 1

              9 8

   输出

              1 2 3 5 4 6 8 9 7

算法思想

       定义一个二维数组,输入每个限制条件时,即表示所学课程(第一个数字)的入度加一,

输入完毕之后,执行程序。

(1)       查询第一个入度为0的课程序号,说明此课程没有先修课程,可以直接开始学习;

(2)       记录课程序号,把入度标记为-1;

(3)       遍历所有课程,把前驱含有上述课程序号的课程入度减一;

(4)       循环完毕,输出记录。

实现过程

 

1

2

3

4

5

6

7

8

9

后驱

3,8

3,5

4

6,7

4,6

 

 

9

7,

前驱

 

 

1,2,

3,5

2,

4,5,

4,9,

1,

8,

前驱为0的序号为1,记录A[i++]=1,标记为-1;然后把1的后驱的课程序号的入度减一。得到下表。

 

1

2

3

4

5

6

7

8

9

后驱

 

3.,5

4

6,7

4,6

 

 

9

7

前驱

-1

 

2

3,5

2

4,5

4,9

 

8

前驱为0的序号为2,记录A[i++]=2,标记为-1;然后把2的后驱的课程序号的入度减一。

重复此过程,直到没有入度(前驱)为0的点,得到的A[]序列即为所求课程合法安排。

当A[]中的点的个数少于总数时,说明存在环,不能形成拓扑排序。

代码实现

一:二维数组

#include<stdio.h>

#include<iostream>

using namespace std;

int map[510][510]={0};//存储有关联的点

int indegree[510]={0};//存储每个点的入度

int queue[510];//存储所求序列

void topo(int n)

{

       int m,t=0;

       for(int i=1;i<=n;i++)

       {

              for(int j=1;j<=n;j++)//每次循环找到第一个入度为0的点

              {

                     if(indegree[j]==0)

                     {

                            m=j;

                            break;

                     }

                    

              }

              queue[t++]=m;//将入度为0的点存进数组

              indegree[m]=-1;//标记已经记录过的点

              for(int k=1;k<=n;k++)//将查询到入度为0点相连点的入度减一

              {

                     if(map[m][k])

                            indegree[k]--;

              }

       }

       printf("%d",queue[0]);

       for(int i=1;i<n;i++)

             printf(" %d",queue[i]);

       printf("\n");

}

int main()

{

       int n,m;

       scanf("%d%d",&n,&m);

       for(int i=0;i<m;i++)

       {

              int a,b;

              scanf("%d%d",&a,&b);

              if(map[b][a]==0)//防止数据重复

              {

                     map[b][a]=1;

                     indegree[a]++;

              }

       }

       topo(n);

       return 0;

      

}

 

二:邻接表

  1 #include<stdio.h>
  2 
  3 #include<iostream>
  4 
  5 #include<string.h>
  6 
  7 using namespace std;
  8 
  9 int indegree[510]={0};
 10 
 11 int queue[510];
 12 
 13 struct Node
 14 
 15 {
 16 
 17        int next;
 18 
 19        int to;
 20 
 21 }A[510];
 22 
 23 int head[510];
 24 
 25 void topo(int n)
 26 
 27 {
 28 
 29        int m,t=0;
 30 
 31        for(int i=1;i<=n;i++)
 32 
 33        {
 34 
 35               for(int j=1;j<=n;j++)
 36 
 37               {
 38 
 39                      if(indegree[j]==0)
 40 
 41                      {
 42 
 43                             m=j;
 44 
 45                             break;
 46 
 47                      }
 48 
 49               }
 50 
 51               queue[t++]=m;
 52 
 53               indegree[m]=-1;
 54 
 55               for(int k=head[m];k!=-1;k=A[k].next)
 56 
 57               {
 58 
 59                      indegree[A[k].to]--;
 60 
 61               }
 62 
 63        }
 64 
 65        printf("%d",queue[0]);
 66 
 67        for(int i=1;i<n;i++)
 68 
 69               printf(" %d",queue[i]);
 70 
 71        printf("\n");
 72 
 73 }
 74 
 75 int main()
 76 
 77 {
 78 
 79        int n,m;
 80 
 81        memset(head,-1,sizeof(head));
 82 
 83        scanf("%d%d",&n,&m);
 84 
 85        for(int i=1;i<=m;i++)
 86 
 87        {
 88 
 89               int a,b;
 90 
 91               scanf("%d%d",&a,&b);
 92 
 93               A[i].to=a;
 94 
 95               A[i].next=head[b];
 96 
 97               head[b]=i;
 98 
 99               indegree[a]++;
100 
101              
102 
103        }
104 
105        topo(n);
106 
107        return 0;
108 
109       
110 
111 }
112 
113  

 

三:队列

  1 #include<stdio.h>
  2 
  3 #include<iostream>
  4 
  5 #include<queue>
  6 
  7 int map[510][510]={0};
  8 
  9 int indegree[510]={0};
 10 
 11 int que[510];
 12 
 13 using namespace std;
 14 
 15 void topo(int n)
 16 
 17 {
 18 
 19        priority_queue<int,vector<int>,greater<int> >q;
 20 
 21        int m,t=0;
 22 
 23        for(int i=1;i<=n;i++)
 24 
 25        {
 26 
 27               if(indegree[i]==0)
 28 
 29               q.push(i);
 30 
 31        }
 32 
 33  
 34 
 35        while(!q.empty())
 36 
 37        {
 38 
 39               int top=q.top();
 40 
 41               q.pop();
 42 
 43               indegree[top]=-1;
 44 
 45               que[t++]=top;
 46 
 47               for(int j=1;j<=n;j++)
 48 
 49               {
 50 
 51                      if(map[top][j])
 52 
 53                      {
 54 
 55                             indegree[j]--;
 56 
 57                             if(indegree[j]==0)
 58 
 59                                    q.push(j);
 60 
 61                      }
 62 
 63               }
 64 
 65                     
 66 
 67        }
 68 
 69        printf("%d",que[0]);
 70 
 71        for(int i=1;i<n;i++)
 72 
 73               printf(" %d",que[i]);
 74 
 75        printf("\n");
 76 
 77 }
 78 
 79 int main()
 80 
 81 {
 82 
 83        int n,m;
 84 
 85        scanf("%d%d",&n,&m);
 86 
 87        for(int i=0;i<m;i++)
 88 
 89        {
 90 
 91               int a,b;
 92 
 93               scanf("%d%d",&a,&b);
 94 
 95               if(map[b][a]==0)
 96 
 97               {
 98 
 99                      map[b][a]=1;
100 
101                      indegree[a]++;
102 
103               }
104 
105        }
106 
107        topo(n);
108 
109        return 0;
110 
111 }

 

运行截图

以下是用第三种方法队列运行程序得到的结果:

 

 

个人总结

       第一种方法简单易懂,但缺点是用二维数组存储数据时容易超内存,而题目往往要求的数据较大,大多数题目不能用这种方法解题。第二种方法用邻接表,可以实现大量数据的存储,但邻接表每个节点的查询较难看懂,最好在纸上推演。有些题目不仅要求拓扑排序,还会有其他的要求,对于每个数据有不同的权重,队列可以完美地解决这类问题,例题http://acm.hust.edu.cn/vjudge/contest/128662#problem/A

       这三种方法的核心算法思想是一样的,只是在具体的实现过程中用了不同的数据存储方式,可以适应不同的情况。

 

posted on 2016-08-22 14:54  左岸zero  阅读(131)  评论(0编辑  收藏  举报

导航