Description
在一个 qq 群里有着许多师徒关系,如 A 是 B 的师父,同时 B
是 A 的徒弟,一个师父可能有许多徒弟,一个徒弟也可能会有许多不同的师父。
输入给出该群里所有的师徒关系,问是否存在这样一种非法的情况:以三个人为
例,即 A 是 B 的师父,B 是 C 的师父,C 又反过来是 A 的师父。若我们将该群
里的所有人都抽象成图上的结点,将所有的师徒关系都抽象成有向边(由师父指
向徒弟),该实际问题就转化 为一个数学问题——该图上是否存在一个环,即判
断该 图是否为有向无环图。
Input
给定N个节点,M个边(2 <= N, M <= 100).N = 0.结束输入。
Output
输出是否有环。
Sample Input
3 2
0 1
1 2
2 2
0 1
1 0
0 0
Sample Output
YES
NO
Solution
拓扑排序板题。
#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;
vector<int> edge[101]; //邻接链表,因为边不存在权值,只需保存与其邻接的结点编号即可, 所以vector中的元素为int
queue<int> Q; //保存入度为0的结点的队列
int main()
{
// freopen("in.txt", "r", stdin);
int inDegree[101]; //统计每个结点的入度
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(); //清空邻接链表,使用clear()效率高,15ms AC
// vector<int>().swap(edge[i]);//使用回收内存方法效率低,30ms AC
}
while (m--)
{
int a, b;
scanf("%d%d", &a, &b); //读入一条由a指向b的有向边
inDegree[b]++; //又出现了一条弧头指向b的边,累加结点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)
{ //当队列中入度为0的结点未被取完时,重复
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)
{ //若该结点入度变为0
Q.push(edge[nowP][i]); //将其放入队列当中
}
}
}
if (cnt == n)
puts("YES");
// printf("YES\n"); //若所有结点都能被确定拓扑序列,则原图为有向无环图,puts() 15ms AC,printf() 30ms AC
else
puts("NO");
// printf("NO\n"); //否则,原图为非有向无环图
}
return 0;
}