换个形象点的解释,我们在学习一门课程之前,应该需要一定的预备知识,比如在学习B课程之前我们需先学习A(后用< X,Y > 表示X课程是Y课程的预备知识,其实与上述有序偶的含义相同),则有 < A,B >。我们还有 < C,B >, < B,D >, < E,D >, < D,F >, < D,G >, < H,G >. 现在要求你合理安排A-H这些课程的学习顺序。这个任务的要求实际上就是对A-H进行拓扑排序。
以上面给课程排序为例,我们首先要学的,一定是一个不需要任何预备知识的课程,然后学完这个课程之后,根据边的关系再看有哪些新的课程可以学习,同时我们还要清楚,学完一门课程后,就不需要再次学习这一门课程了。
我们将其与图做个类比。
不需要预备知识的课程-> 入度为0的点
新的课程->所指向的下一个点
每门课之学一次->从图中删除节点 && 从图中删除有向边
接着再根据图类比结果决定存储的数据
入度为0的点->需要存储每个节点的入度
所指向的下一个点->需要存每个节点的后继
删除节点与有向边-> 这里讨论一下:
如果我们真的删除了节点和有向边,那就意味着无法在找到这些数据了。因为我们还需要判断是否形成了DAG,最保险的做法是将下一个节点的入度-1。如果发现某个节点的入度为-1了,表明存在有向环,那么说明不存在与拓扑排序。
要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
using namespacestd;
const int maxn =505;
int indegree[maxn];
vector<int> mp[maxn];
queue<int> q,ans;
void init(int n)
{
memset(indegree,0,sizeof(indegree));
for (int i =1; i <= n; i ++) {
mp[i].clear();
}
while (!q.empty()) {
q.pop();
}
while(!ans.empty())
{
ans.pop();
}
}
bool solve(int n)
{
bool succ =true;
while(1)
{
for (int i =1; i <= n ; i ++) {
if (indegree[i] ==0) {//每次寻找入度为0的点,即不用其他知识即可以直接学的,加入队列。
q.push(i);
ans.push(i);
indegree[i] = -1;
break;//该题要求输出编号小的在前,所以每次取编号最小的入度为0的点开始。若不要求,该行break可删除。
}
}
if (q.empty()) {
break;
}
while (!q.empty()) {
int t =q.front();q.pop();
for (int i =0; i <mp[t].size(); i ++) {
if (indegree[mp[t][i]] == -1) {//如果下一个点入度为-1,说明在之前已经学过,形成了环,所以不能进行拓扑排序
succ = false;
break;
}
elseindegree[mp[t][i]] --;//每次去掉所指向的点的一条边,当一个点没有被指向,入度为0时,就可以被学习
}
mp[t].clear();
if (!succ) {
break;
}
}
if (!succ) {
break;
}
}
if (ans.size() != n) {//
succ = false;
}
return succ;
}
int main()
{
int n,m;
while (scanf("%d%d",&n,&m) !=EOF) {
int a,b;
init(n);
for (int i =0; i < m; i ++) {
scanf("%d%d",&a,&b);
mp[a].push_back(b);
indegree[b] ++;
}
if(solve(n))
{
printf("%d",ans.front()) ;ans.pop();
while (!ans.empty()) {
printf(" %d",ans.front());
ans.pop();
}
printf("\n");
}
}
return0;
}