问题描述:
给出一张有向图,问它是否存在环。
解题思路:
这里可以用到拓扑排序。
-
拓扑排序的定义
拓扑排序应用于有向无环图之中,排序完以后会出现这样的性质:对于一个点p,只对排序位置在它之后的点有边。如果有环,则环上的点以及环上点所能到达的点都不会被放进拓扑序列中。
举例来说,如果我们将一系列需要运行的任务构成一个有向图,图中的有向边则代表某一任务必须在另一个任务之前完成这一限制。那么运用拓扑排序,我们就能得到满足执行顺序限制条件的一系列任务所需执行的先后顺序。当然也有可能图中并不存在这样一个拓扑顺序,这种情况下我们无法根据给定要求完成这一系列任务,这种情况称为循环依赖(circular dependency)。
我们可以这样来理解拓朴排序:
一项大的工程可以看作是由若干个称为“活动”的子工程组成的集合,这些子工程之间必定存在一种先后关系,即某些子工程必须在其它一些工程完成之后才能开始,我们可以用有向图表示工程间关系,子工程(活动)为顶点,活动之间的先后关系为有向边,这种有向图称为顶点表示活动的网络,又称为AOV网。在AOV网中,如果有一条从顶点Vi到Vj的路径,则说Vi是Vj的前趋,Vj是Vi的后续。如果有弧<Vi,Vj>,则称Vi是Vj的直接前趋,Vj是Vi的直接后续。
拓扑排序是把AOV网中所有顶点排成一个线性序列(拓扑序列),如果有弧<Vi,Vj>,则序列中顶点Vi在Vj之前。一个存在回路的有向图顶点是不能排成拓扑序列的,因为有回路的有向图的边体现的先后关系不是非自反的。任何没有回路的有向图,其顶点都可以排成一个拓扑序列。而且得到的拓扑序列是不唯一的。如对下图进行拓扑排序可以得到多个拓扑序列,如:02143567、01243657、02143657、01243567。 -
拓扑排序的步骤
- 找到一个入度为0的点,将它放入拓扑序列中。
- 删除这个点所关联的所有边,即它的子节点的入度全部-1
- 回到第一步,知道所有的点已被删除。
- 拓扑排序的应用
由于根据拓扑排序的定义以及实现,若图中存在环,则拓扑排序会中断,即找不到一个入度为0的点,也就是说若图中存在环,拓扑排序后的元素个数不足 n n n。
因此,我们只需要将图的拓扑序的长度与节点数比较,若相等则图为 D A G DAG DAG (有向无环图),否则则存在环。
CODE:
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int> g[100010];
int top[100010],in[100010],t=0;
queue<int> q;
bool vis[100010]={false};
void topsort(int s)
{
q.push(s); //找到第一个入度为0的点放入队列
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=true; //表示已经删除了
top[++t]=u; //记录
for(int i=0;i<g[u].size();i++)
{
in[g[u][i]]--; //删边
if(!in[g[u][i]]&&!vis[g[u][i]]) q.push(g[u][i]); //若有入度为0的点则放入队列
}
}
}
int main()
{
cin>>n>>m;
int a,b;
for(int i=1;i<=m;i++)
{
cin>>a>>b;
g[a].push_back(b);
in[b]++; //记录入度
}
for(int i=1;i<=n;i++)
if(!in[i])
{
topsort(i);
break;
}
if(t==n) cout<<"Yes";
else cout<<"No";
cout<<endl;
//for(int i=1;i<=t;i++)
//cout<<top[i]<<" ";
return 0;
}