你不要问我为什么没有关键路径
这一篇可能比倍增那篇还WATER
8.1 拓扑排序
拓扑排序,是一种运用于有向无环图中的一种算法(本质上是一种搜索),也可以用于判断有向图中是否有环
其基本思想是:
- 取出图中入度为 0 0 0 的节点,压入答案数组中
- 删除该节点以及相关的边
- 重复以上操作,直到再也找不到入度为 0 0 0 的节点
完成拓扑排序后,判断:若图中还有节点,说明图中有环,反之,无环
8.2 举例时间
我们结合下面的图举个例子:
不要注意上面这幅图的正确性
首先,我们找到一个入度为
0
0
0 的边,即AK lhy的考试
,并压入答案数组中
随后,删除该节点以及相关的边
这样一来,我们就得到了下图:
那么,在这幅图中,出现了,两个入度为
0
0
0 的点:AK CSP-J
和 AK CSP-S
一般情况下,题目会有特定的输出规则,比如字典序
这里,我们没有特殊规则,就随便取一个AK CSP-J
那么,此时,我们就只能取 AK CSP-S
了
同理,我们可以得到接下来若干步的结果了
这里,我们选择取 AK NOIP 普及组
这里,选择AK WC
那么,这样一来,我们就得到了最终结果:
AK lhy的比赛
→
\rightarrow
→ AK CSP-J
→
\rightarrow
→ AK CSP-S
→
\rightarrow
→ AK NOIP 普及组
→
\rightarrow
→ AK NOIP 提高组
→
\rightarrow
→ AK WC
→
\rightarrow
→ AK 省选
→
\rightarrow
→ AK NOI
→
\rightarrow
→ AK IOI
那么,为什么说拓扑排序可以判断有向图中是否环呢?
考虑下面这幅图:
这时候,因为出现了环,所以当排序到某一步后,必然会出现图中没有一个入度为 0 0 0 的情况,此时,我们不会选择任何节点,图中也就必然会留下节点
所以说,若排序后图中还有节点,说明图中有环
8.3 代码实现
我们以判断有向图中是否有环来举个例子:
#include<queue>
#include<vector>
#include<cstdio>
using namespace std;
const int N=1005;
int G[N]; //记录入度
bool k[N]; //记录是否遍历过
vector<int> v[N]; //邻接矩阵存图
queue<int> q;
int n,m,x,y;
void bfs(){
while(!q.empty()){
int x=q.front();
q.pop();
k[x]=1; //标记,已遍历
for(vector<int>::iterator it=v[x].begin();it!=v[x].end();it++){
G[*it]--; //删除与 x 节点有关的边
if(!G[*it]){ //出现了新的入度为 0 的边
q.push(*it); //压
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
G[y]++; //入度自增
v[x].push_back(y); //存图
}
for(int i=1;i<=n;i++){
if(!G[i]){ //找到入度为 0 的顶点
q.push(i); //压入队列
}
}
bfs(); //拓扑排序
for(int i=1;i<=n;i++){ //遍历每一个节点
if(!k[i]){ //有节点未被遍历(图中有剩余节点)
printf("NO"); //有环
return 0;
}
}
printf("YES"); //无环
return 0;
}