拓扑排序是对有向无环图(DAG)进行排序,从而找到一个序列。该序列满足对于任意一对不同的顶点u,v∈V,若G中存在一条从u->v的边,则在此序列中u在v前面。
拓扑排序也可以用来判断一个有向图是否存在环。
有两种算法可以求得该序列:
1.Kahn算法。
其实就是不断的寻找有向图中没有前驱(入度为0)的顶点,将之输出。然后从有向图中删除所有以此顶点为尾的弧。重复操作,直至图空,或者找不到没有前驱的顶点为止。
该算法还可以判断有向图是否存在环(存在环的有向图肯定没有拓扑序列),通过一个count记录找出的顶点个数,如果少于N则说明存在环使剩余的顶点的入度不为0。(degree数组记录每个点的入度数)
#include<bits/stdc++.h>
#define N 100010
using namespace std;
namespace program{
queue<int>q;
list<int>ans;
int Next[N],head[N],to[N],tot=0;
int in1[N],n,m;
inline void init(){
tot=0;
memset(in1,0,sizeof in1);
memset(head,-1,sizeof head);
}
inline void add(int u,int v){
tot+=1;
Next[tot]=head[u];
to[tot]=v;
head[u]=tot;
}
inline bool Kahn(){
int cnt=0;
while(!q.empty()){
int k=q.front();
q.pop();
cnt+=1;
ans.push_back(k);
for(int i=head[k];i^(-1);i=Next[i]){
in1[to[i]]-=1;
if(!in1[to[i]])
q.push(to[i]);
}
}
if(cnt^n)
return 0;
return 1;
}
inline void work(){
int u,v;
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>u>>v;
add(u,v);
in1[v]+=1;
}
for(int i=1;i<=n;i++){
if(!in1[i])
q.push(i);
}
if(Kahn()){
list<int>::iterator it;
for(it=ans.begin();it!=ans.end();it++)
cout<<*it<<" ";
return;
}else{
puts("NO");
}
}
}
int main(){
program::work();
return 0;
}
2.基于DFS的求解方法。
算法导论对于该种方法讲述的比较详细,由于它用的单链表存边,下面我只贴一份自己的模板代码。
思想基于:DFS时候,遇到u->v边,通过在DFS函数快退出时将结点加入到List中实现v在序列的位置始终在u的前面。反向序列即为所求的拓扑序列。
#include<bits/stdc++.h>
#define N 100010
using namespace std;
namespace program{
int tot,head[N],Next[N],to[N];
bool limit[N];
int n,m;
list<int>ans;
inline void init(){
tot=0;
memset(limit,0,sizeof limit);
memset(head,-1,sizeof head);
}
inline void add(int u,int v){
tot+=1;
to[tot]=v;
Next[tot]=head[u];
head[u]=tot;
}
inline void dfs(int k){
limit[k]=1;
for(int i=head[k];i^-1;i=Next[i]){
if(!limit[to[i]])
dfs(to[i]);
}
ans.push_back(k);
}
inline void work(){
int u,v;
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>u>>v;
add(u,v);
}
for(int i=1;i<=n;i++)
if(!limit[i])
dfs(i);
list<int>::reverse_iterator it;
for(it=ans.rbegin();it!=ans.rend();it++)
cout<<*it<<" ";
puts("");
return;
}
}
int main(){
program::work();
return 0;
}