有向无环图-拓扑排序
题意
在一个QQ群里有着许多的师生关系,如A 是B的师傅,同时B是A的徒弟,一个师父可以有许多徒弟,一个徒弟也可以有许多师父。输入该群里所有的师徒关系,问是否存在这样一种非法的情况:以三个人为例,即A是B的师父,B是C的师父,C反过来又是A的师父。
若我们将群里所有人都抽象成图上的节点,将师徒关系抽象成有向边(由师父指向徒弟),则该实际问题就转化为一个数学问题,判断该图是不是有向无环图–拓扑排序。
/*拓扑排序定义:
对一个有向无环图,所有的有向边为(U,V)(由U指向V),在拓扑排序中节点U都排列在节点V之前,
即若节点U经过若干条有向边后能够到达节点V,则在求得的序列中U必排在V之前*/
/*如何求一个有向无环图的拓扑序列即拓扑排序的方法:
选择一个入度为0的节点,作为序列的第一个节点,
当该节点被选为序列的第一个顶点后,将该点从图中删去,同时删去以该节点为弧尾的所有有向边,得到一个新图,重复以上操作。
若所有的节点尚未被删去时即出现了找不到入度为0的节点,则说明剩余的节点形成一个环路,拓扑排序失败,原图不存在拓扑序列*/
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<int> edge[501];//邻接链表,因为边不存在权值,只需保存与其邻接的节点编号即可
queue<int> Q;//保存入度为0的节点的队列
int main(){
int inDegree[501];//存储每个节点的入度
int n, m;
cin >> n >> m;
if (n == 0 || m == 0){
return 0;
}
for (int i = 0; i < n; i++){
inDegree[i] = 0;//初始化入度信息
edge[i].clear();//清空邻接链表
}
int a, b;
while (m--){
cin >> a >> b;//读入一条由a指向b的有向边
inDegree[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;//累加已经确定的拓扑排序的节点个数
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){//若该节点入度为0,则加入队列
Q.push(edge[nowP][i]);
}
}
}
if (cnt == n){//若所有节点都能被确定为拓扑序列,则原图为有向无环图
cout << "YES" << endl;
}
else{
cout << "NO" << endl;
}
return 0;
}