说明:本文章参考刘汝佳《算法竞赛入门经典》(第2版)
拓扑排序
关于拓扑排序最经典的问题应该是排课程的问题,在此不再赘述。举一个例子,已知a<b,c<b,d<b,则a,b,c,d从小到大的顺序可能是a,c,d,b,也可能是a,d,c,b等其他情况,拓扑排序就是只需要给出任意一种情况即可。我们可以把上面的小于号当成图的有向边,a,b,c,d当成图的结点,就可以转换成有向图的问题了。很显然,如果图中存在有向环,则不存在拓扑排序,反之,则存在。不含有向环的有向图成为有向无环图(Directed Acyclic Graph, DAG)。
例题 确定比赛名次 HDU - 1285
【代码】
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=500+5;
int n,m;
int graph[maxn][maxn];
int indegree[maxn];
int topo[maxn];
int x,y;
bool toposort(){
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(indegree[j]==0){
topo[cnt++]=j;
indegree[j]--;
for(int k=1;k<=n;k++){
if(graph[j][k]==1){
graph[j][k]=0;
indegree[k]--;
}
}
break;//注意此处有break,此break可以保证较小编号在前(例子:1 3,3 4,2 4
//若无break,拓扑序:1 3 4 2,有break,拓扑序:1 2 3 4
}
}
}
//判断是否有环
for(int i=1;i<=n;i++){
if(indegree[i]==0) return false;//上面进行了n次,还有度为0的点,说明有环
}
return true;
}
int main()
{
while(cin>>n>>m){
if(n==0) break;
memset(graph,0,sizeof(graph));
memset(indegree,0,sizeof(indegree));
for(int i=1;i<=m;i++){
cin>>x>>y;
if(!graph[x][y]) indegree[y]++;//防止重边输入,很坑
graph[x][y]=1;
}
toposort();
int first=1;
for(int i=0;i<n;i++){
if(first){
cout<<topo[i];
first=0;
}
else
cout<<" "<<topo[i];
}
cout<<endl;
}
return 0;
}
例题 Ordering Tasks UVA - 10305
【代码一】
类似上题代码
【代码二(dfs版本)不能保证较小编号的在前面】
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100+5;
int n,m;
int graph[maxn][maxn];
int vis[maxn];//-1代表正在访问,0代表未访问,1代表已经访问过
int topo[maxn];
int x,y;
int cnt;
bool dfs(int v){
vis[v]=-1;
for(int i=n;i>=1;i--){
if(graph[v][i]){
if(vis[i]<0) return false;//存在有向环
else if(!vis[i] && !dfs(i)) return false;//递归看看子孙中是否有有向环
}
}
vis[v]=1;topo[--cnt]=v;
return true;
}
bool toposort(){
memset(vis,0,sizeof(vis));
for(int i=n;i>=1;i--){
if(!vis[i])
if(!dfs(i))
return false;
}
}
int main()
{
while(cin>>n>>m){
if(n==0) break;
memset(graph,0,sizeof(graph));
for(int i=1;i<=m;i++){
cin>>x>>y;
graph[x][y]=1;
}
cnt=n;
toposort();
int first=1;
for(int i=0;i<n;i++){
if(first){
cout<<topo[i];
first=0;
}
else
cout<<" "<<topo[i];
}
cout<<endl;
}
return 0;
}