递归练习 uva 10305

本文深入探讨了拓扑排序的概念、定义及其在现实生活中的应用。通过详细解释拓扑排序的过程,包括两种主要的解题方法:一种是贪心算法,另一种则是使用深度优先搜索。文中还提供了代码示例,帮助读者理解并实现拓扑排序。此外,文章强调了拓扑排序在解决实际问题中的重要性,如项目管理、课程安排等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

//题意:输入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 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值