//题意:输入n和m,以及m个二元组(i,j),求1~n的一个排列使得对于每个(i,j),i在j的前面(拓扑排序)
#include<cstdio>
#include<cstring>
const int maxn = 1000;
int n, m, G[maxn][maxn], c[maxn], topo[maxn], t;
bool dfs(int u,int deep){
// for(int i=0;i<deep ;i++){ //debug
// printf(" ");
// }
// printf("%d %d\n",u,deep);
c[u] = -1;
for(int v = 0; v < n; v++) if(G[u][v]) {
if(c[v]<0) return false;
else if(!c[v]) dfs(v,deep+1);
//else if(!c[v] && !dfs(v) ) return false;
}
c[u] = 1; //千万不能省,可能正确的数据输出no。因为dfs过程中需要区分是环(dfs还在栈帧中,尚未返回)
//还是只是以前访问过,并且递归访问过他的所有子孙
topo[--t]=u;
return true;
}
bool toposort(){
t = n;
memset(c, 0, sizeof(c));
for(int u = 0; u < n; u++) if(!c[u])
if(!dfs(u,0)) return false;
return true;
}
int main() {
while(scanf("%d%d", &n, &m) == 2 && n) {
memset(G, 0, sizeof(G));
for(int i = 0; i < m; i++) {
int u, v;
scanf("%d%d", &u, &v); u--; v--;
G[u][v] = 1;
}
if(toposort()) {
for(int i = 0; i < n-1; i++)
printf("%d ", topo[i]+1);
printf("%d\n", topo[n-1]+1);
}
else
printf("No\n"); //题目没说无解输出什么,应该是保证有解吧
}
}
题目大意:
约翰有n个任务要做, 不幸的是,这些任务并不是独立的,执行某个任务之前要先执行完其他相关联的任务。
分析与总结:
这题是最基础的拓扑排序。
拓扑排序的定义:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。
以上的定义比较“学术“,简单的说,就是一些事件,某些必须在另外某些发生之前; 比如穿袜和穿鞋;再比如大学选课,选修某门课的前提必须先修完其他一些相关连的课程才可以。
所以,拓扑排序在现实生活中有着十分广泛的应用。
这里有一个拓扑排序过程的演示动画,十分直观:
http://www.jcc.jx.cn/xinwen3/news/kj/flash/2009/0426/1302.htm
一般的题目可能会给一些事件,和一些发生次序,来求一个拓扑排序;
对于拓扑排序 主要有两种方法。
第一种是贪心的思路, 以邻接表为图的存储结构,过程直接模拟上面的演示动画:
a)扫描顶点表,将入度为零的顶点入栈; (ps:这里栈可以使优先队列 and so on。。)
b)当栈非空时:
输出栈顶元素v,出栈;
检查v的出边,将每条出边的终端顶点的入度减1,若该顶点入度为0,入栈;
c)当栈空时,若输出的顶点小于顶点数,则说明AOV网有回路,否则拓扑排序完成。
下面是给出这种方法的解题代码:
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N = 105;
int n,m,son[N],topoSort[N],t;
char O[2*N];
vector<int>G[N];
void init(){
for(int i=1; i<=n; ++i){
G[i].clear();
}
memset(son, 0, sizeof(son));
}
int main(){
int u,v;
while(scanf("%d%d",&n,&m)&&n+m){
init();
for(int i=0; i<m; ++i){
scanf("%d%d",&u,&v);
G[u].push_back(v);
++son[v];
}
int num=n;
queue<int>q;
for(int i=1; i<=n; ++i){
if(!son[i]) q.push(i);
}
int pos=0;
while(!q.empty()){
int t=q.front();
q.pop();
topoSort[pos++] = t;
for(int v=0; v<G[t].size(); ++v)
if(--son[G[t][v]]==0)
q.push(G[t][v]);
}
for(int i=0; i<pos; ++i){
if(!i)printf("%d",topoSort[i]);
else printf(" %d",topoSort[i]);
}
printf("\n");
}
return 0;
}
第二种方法是《算法导论》上的, 刘汝佳的《算法入门经典》P 111页上介绍的也是这种方法。
这种方法是用dfs,从根节点开始深搜,每搜到一点记录下来开始搜索和结束搜索的时间戳;
然后根据结束时间从大到小排序即可。这种方法时间效率更高,更推荐。
推荐的解法:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int G[110][110],n,m,a,b;
int vis[110],c[110], topo[110], t;
bool dfs(int u){
vis[u] = -1; //表示正在访问
for(int v=1; v<=n; ++v) if(G[u][v]){
if(vis[v] == -1) return false; // 如果存在有向环,失败退出
else if(!vis[v] && !dfs(v)) return false;
}
// 结束访问
vis[u] = 1; topo[--t] = u;
return true;
}
bool topoSort(){
t = n;
memset(vis, 0, sizeof(vis));
for(int u=1; u<=n; ++u)
if(!vis[u] && !dfs(u)) return false;
return true;
}
int main(){
#ifdef LOCAL
freopen("input.txt","r",stdin);
#endif
// 注意输入那里的结束条件不能是 n&&m,因为m可能是0
while(~scanf("%d %d",&n,&m) && n+m){
memset(G, 0, sizeof(G));
for(int i=0; i<m; ++i){
scanf("%d %d",&a,&b);
G[a][b] = 1;
}
if(topoSort()) {
printf("%d",topo[0]);
for(int i=1; i<n; ++i)
printf(" %d",topo[i]);
printf("\n");
}
}
return 0;
}
—— 生命的意义,在于赋予它意义。
原创 http://blog.youkuaiyun.com/shuangde800 , By D_Double
---------------------
作者:shuangde800
来源:优快云
原文:https://blog.youkuaiyun.com/shuangde800/article/details/7727280
版权声明:本文为博主原创文章,转载请附上博文链接!