Fork me on GitHub

数据结构与算法-图论(四)-拓扑排序


数据结构与算法-图论(四)-拓扑排序

设有一个有向无环图(DAG图),对其进行拓扑排序即求其中结点的一个拓扑序列,对于所有的有向边(U,V)(由U指向V),在该序列中结点U都排列在结点V之前。满足该要求的结点序列,被称为满足拓扑次序的序列。求这个序列的过程,被称为拓扑排序。

拓扑排序的方法

首先,所有有入度(即以该结点为弧头的弧的个数)的结点均不可能排在第一个。那么选择一个入度为0的结点,作为序列的第一个结点。当该结点被选为序列的第一个顶点后,该结点从图中删去,同时删去以该结点为弧尾的所有边,得到一个新图。那么这个新图的拓扑序列即为原图的拓扑序列中除去第一个结点后剩余的序列。同样,在新图上选择一个入度为0的结点,将其作为原图的第二个结点,并在新图中删去该点以及以该点为弧尾的边。这样又得到了一张新图,重复同样的方法,直到所有的结点和边都从原图中删去。若在所有结点尚未被删去时即出现了找不到入度为0的结点的情况,则说明剩余的结点形成了一个环路,拓扑排序失败,原图不存在拓扑序列。

题目描述:

ACM-DIY is a large QQ group where many excellent acmers get together.It is so harmonious that just like a big family. Every day,many “holy cows” like HH,hh,AC,ZT,lcc,BF,Qinz and so on chat on-line to exchange their ideas.When someone has questions, many warm-hearted cows like Lost will come to help. Then the one being helped will call Lost “master”, and Lost will have a nice “prentice”. By and by, there are many pairs of “masters and too many prentices”, how can we know whether it is legal or not? We all know a master can have many prentices and a prentice may have a lot of masters too, it’s legal. Nevertheless, some cows are not so honest, they hold illegal relationship. Take HH and 3xian for instant, HH is 3xian’s master and, at the same time, 3xian is HH’s master, which is quite illegal! To avoid this, Please note that the “master and prentice” relation is transitive. It means that if A is B’s master and B is C’s master, then A is C’s master.

输入:

The input consist of several test cases. For each case, the first line contains two integers, N(member to be tested) and M (relationships to be tested)(2 <= N,M<= 100).Then M lines follow, each contains a pair of (x,y) which means x is y’s master and y is x’s prentice. The input is terminated by N = 0. To MAKE IT SIMPLE, we give every one a number(0, 1, 2, … , N-1). We use their numbers instead of their name.

输出:

For each test case, print in one line the judgement of the messy relationship. If it is legal, output “YES”, otherwise “NO”.

样例输入:

1
2
3
4
5
6
7
3 2
0 1
1 2
2 2
0 1
1 0
0 0

样例输出:

1
2
YES
NO

无论何时,当需要判断某个图是否属于有向无环图时,我们都需要立刻联想到拓扑排序。若一个图,存在符合拓扑次序的结点序列,则该图为有向无环图;反之,该图为非有向无环图。也就是说,若在该图上拓扑排序成功,该图为有向无环图;反之,则存在环路。

代码如下:

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
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>

using namespace std;

vector<int> edge[501]; // 邻接链表,因为边不存放权值,只需要保存与其邻接的结点编号即可,所以vector中的元素为int
queue<int> Q; // 保存入度为0的结点的队列
int main(){
int inDegree[501]; // 统计每个结点的入度
int n,m;
while(scanf("%d%d",&n,&m)!=EOF){
if(n == 0 && m == 0) break;
for(int i = 0 ; i < n ; i++){ // 初始化所有结点,注意本题结点编号由0到n-1
inDegree[i] = 0; // 初始化入度信息,所有结点入度均为0
edge[i].clear(); // 清空邻接链表
}
while(m--){
int a,b;
scanf("%d%d",&a,&b); // 读入一条由a指向b的有向边
inDegree[b]++;
edge[a].push_back(b); // 将b加入a的邻接链表
}
while(Q.empty() == false) Q.pop(); // 若队列非空,则一直弹出队头元素,该操作的目的为清空队列中所有的元素(可能为上一组测试数据中遗留的数据)
for(int i = 0 ; i < n ; i++){
if(inDegree[i] == 0) Q.push(i); // 若结点入度为0,则将其放入队列
}
int cnt = 0; // 计数器,初始值为0,用于累加已经确定拓扑序列的结点个数
while(Q.empty() == false){
int nowP = Q.front(); // 读出队头结点编号,本例不需要求出确定的拓扑序列,固不做处理;若要求求出确定的拓扑次序,则将该结点紧接着放在已经确定的拓扑序列之后
Q.pop();
cnt++; // 被确定的结点个数加一
for(int i = 0 ; i < edge[nowP].size(); i++){
inDegree[edge[nowP][i]]--;
if(inDegree[edge[nowP][i]] == 0){
Q.push(edge[nowP][i]);
}
}
}
if(cnt == n) puts("YES");
else puts("NO");
}
return 0;
}

运行结果:

该代码所有结点至多进入队列一次,但在每个结点被取出时我们都要遍历以其为弧尾的边,固复杂度为O(N+E),其中N为结点的个数,E为边的个数

如本例所示,很多涉及拓扑排序的问题都没有直接给出图,而是将图隐藏在一个实际问题当中,需要考生自己将实际问题抽象成一个图论问题,而这恰恰是所有图论题共同的难点,这种把实际问题抽象出来的能力,需要读者在大量训练中慢慢总结。

坚持原创技术分享,您的支持将鼓励我继续创作
-------------本文结束感谢您的阅读-------------
0%