关于拓扑排序的一些想法

  前几天数据结构课上老师给我们留了一道思考题:如何求出拓扑排序的所有可能路径。说实话,自己的第一感觉就是深搜DFS,但是到最后又被自己推翻了,本来周三的时候想到了一个算法,后来又被自己推翻了。在BestCoder群里问了几个大神,他们也没给出什么好的方法,印象深刻的是有人说我问这种题有意义吗,问得我竟然无法反驳,也是,大多数的人总是要为自己的无知做一些辩解。

  后来整理了一下自己之前所想的,虽然有很大一个漏洞(这就是我推翻我算法的原因),不过自己所想到的算法自己都用代码实现了,也算是一件成功的事情。



大致算法描述:

  按照图的结构给每个结点分一下优先级,设最开始的入度为0的顶点的优先级为1;

  然后接下来将这些入度为0的点射出的边删除,此次操作会产生几个新的入度为的顶点,设这几个顶点的优先级为2;

  继续删除边生成优先级为3的几个顶点,直到图被遍历完;

  最后所有优先级的各顶点全排列就可得到拓扑排序的所有可能路径。

如图:   

    第1优先级:v0

    删除v0射出的三条边,得到的入度为0的点为v1 v2 v3 , 所以第2优先级:v1 v2 v3

    删除v1 v2 v3射出的三条边,得到入度为0的点为v4 v5 , 所以第3优先级: v4 v5

    删除v4 v5射出的三条边,得到入度为0的点为v6 , v7 , 所以第4优先级:v6 v7

    最后第5优先级:v8

  可知优先级靠前的顶点的拓扑排序结果一定是在优先级靠后的顶点之前的,而优先级相同的顶点的前后顺序是没有影响的,所以同一优先级的顶点可以进行全排列,最后将每个优先级的全排列结合在一起就可以得到拓扑排序的所有结果。

  

具体实现:

    优先级的信息要用二维数组Rank[i][j]保存,Rank[i]表示的是第i优先级里的顶点;

    图的信息用二维数组graph[i][j]保存,graph[i][j]表示的是从顶点i连接出去的第j个顶点;

    path[]表示的是拓扑排序的一条路径;

    degree[i]表示的是第i个顶点的入度;

    vis[i]来表示第i个顶点是否被访问过。

      step1 : 先用拓扑排序图中判断是否有环,如果有环输出-1并且结束,无环的话可以进入下一步;

      step2 : 每次用队列来保存入度为0的顶点,然后在Rank数组中保存这些顶点后将这些顶点都弹出,并且删除相关联的边;

      step3 : 最后保存的优先级信息全存储在Rank数组中,这时候需要用DFS即深度优先搜索来遍历Rank数组,输出所有的全排列结果,这时候需要注意的有两点:

          1.每一优先级的全排列用next_permutation()要方便很多;

          2.由于DFS是试探性搜索,所以要注意到回溯。

 

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 1005;                   //默认最大的n的值
vector <int> Rank[maxn] , graph[maxn];       //两个二维数组
int degree[maxn] , n , m , cnt , vis[maxn];    //n是顶点的个数 , m是边的条数 , cnt用来表示有多少个优先级
int path[maxn];        //拓扑排序的路径
bool toposort()        //普通的拓扑排序,即判断是否有环
{
    int i , j , u , v , tot;
    tot = 0;
    memset(degree , 0 ,sizeof(degree));
    for(i = 1 ; i <= n ; i++)
        for(j = 0 ; j < graph[i].size() ; j++)
            degree[ graph[i][j] ]++;
    queue <int> que;            //STL模板实现队列
    for(i = 1 ; i <= n ; i++) {
        if(degree[i] == 0) {
            que.push(i);
            tot++;
        }
    }
    while(!que.empty()) {
        int k = que.front();
        que.pop();
        for(j = 0 ; j < graph[k].size() ; j++) {
            int tmp = graph[k][j];
            degree[tmp]--;
            if(degree[tmp] == 0) {
                que.push(tmp);
                tot++;
            }
        }
    }
    if(tot == n)
        return true;      //返回true表示无环
    else
        return false;
}
void printAll(int n)
{                          //输出一条路径中的结果
    for(int i = 0 ; i < n ; i++)
        printf("%d " , path[i]);
    puts("");
}
void DFS(int i , int &tot)
{                          //深搜,i表示当前为第i优先级,tot表示这时候path[]数组中有多少元素
    if(tot >= n && i >= cnt) {    //递归的终止条件
        printAll(n);      //输出当前的一条路径
        return;
    }
    do {
        for(int j = 0 ; j < Rank[i].size() ; j++)
            path[tot++] = Rank[i][j];        //记录当前优先级的排列信息
        DFS(i + 1 , tot);                    //深搜进入下一步
        tot -= Rank[i].size();                //回溯,tot回到这一层的初始状态值
    } while(next_permutation(Rank[i].begin() , Rank[i].end()));    //STL中的全排列函数
}
void findAll()
{
    int i , j , tot;
    memset(degree , 0 ,sizeof(degree));        //初始化degree[]和vis[]都为0
    memset(vis , 0 , sizeof(vis));
    for(i = 1 ; i <= n ; i++)
        for(j = 0 ; j < graph[i].size() ; j++)
            degree[ graph[i][j] ]++;          //整理每个顶点的入度信息
    cnt = tot = 0;
    queue <int> que;
    while(tot < n) {       //由于建立每一层的优先级的时候队列都是要清空一次的,所以这里用这个循环结束条件
        for(i = 1 ; i <= n ; i++) {
            if(degree[i] == 0 && !vis[i]) {
                que.push(i);
                tot++;
                vis[i]++;
                Rank[cnt].push_back(i);
            }
        }
        while(!que.empty()) {
            int k = que.front();
            que.pop();
            for(j = 0 ; j < graph[k].size() ; j++) {
                int tmp = graph[k][j];
                degree[tmp]--;
            }
        }
        cnt++;
    }
    for(i = 0 ; i < cnt ; i++)
        sort(Rank[i].begin() , Rank[i].end());    //这里sort是为了在DFS的全排列函数中输出所有
    int tmp = 0;
    DFS(0 , tmp);      //i和tot的初始值都是0
}
int main()
{
    int i , j , u , v;
    scanf("%d %d",&n,&m);        //输入顶点数和边数
    while(m--) {
        scanf("%d %d",&u,&v);    //输入每条边的顶点与终点
        graph[u].push_back(v);
    }
    if(toposort()) {            //无环,可进行拓扑排序
        findAll();
    } else {                    //有环,输出-1
        cout<<"-1"<<endl;
    }
    return 0;
}

  

 用老师课件上的图来进行验证:

  

  出于方便,所有顶点都有数字来表示,即1来表示a,2来表示b...所以输入下面一组数据:

  (第一行是n和m,表示有n个顶点和m条边,接下来的n行是每条边的顶点与终点)               

                                    8 10
                                    1 2
                                    2 3
                                    3 4
                                    3 7
                                    5 2
                                    5 3
                                    5 6
                                    6 7
                                    7 4
                                    7 8

  程序的输出结果是:          

                                 1 5 2 6 3 7 4 8

                                 1 5 2 6 3 7 8 4

                                 1 5 6 2 3 7 4 8

                                 1 5 6 2 3 7 8 4 

                                 5 1 2 6 3 7 4 8

                                 5 1 2 6 3 7 8 4

                                 5 1 6 2 3 7 4 8

                                 5 1 6 2 3 7 8 4



  其实这个思路是有漏洞的,因为刚开始的时候如果是两个入度为0的顶点的话那就这两条拓扑排序的路径相互独立了,所以中间还有一些情况无法输出,比如说上图是a和e两个顶点出发的,那么a与f这两点就是相互独立的,f是可以出现a前面的,所以这点很纠结,一直也想不出很好的解决方法,所以就先将这个思路实现了。希望数据结构课上老师能给出很完美的算法。

  ACM中关于拓扑排序的题不多,记得做过的拓扑排序的题是BestCoder第一场的A题,杭电的4857,逆向构图+拓扑排序+优先队列,在这里放一下题目的地址与AC代码:

  题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=4857

 

#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <functional>
#include <vector>
#include <cmath>
#include <string>
#include <stack>
#include <queue>
using namespace std;
const int maxn=100000+5;
vector<int> g[maxn];
int du[maxn],n,m,L[maxn];
void toposort() {
    memset(du,0,sizeof(du));
    for(int i=1;i<=n;i++)
        for(int j=0;j<g[i].size();j++)
            du[g[i][j]]++;
    int tot=0;
    priority_queue<int> Q;
    for (int i = 1; i <= n; i++)
        if (!du[i]) Q.push(i);
    while (!Q.empty()) {
        int x=Q.top();;
        Q.pop();
        for (int i = 0; i < g[x].size(); i++) {
            int t=g[x][i];
            du[t]--;
            if (du[t] == 0)
                Q.push(t);
        }
        L[tot++]=x;
    }
}
int main()
{    int u,v,T,i,j;
    cin>>T;
    while(T--)
    {    scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)
            g[i].clear();
        while(m--)
        {    scanf("%d%d",&u,&v);
            g[v].push_back(u);
        }
        toposort();
        printf("%d",L[n-1]);
        for(i=n-2;i>=0;i--)
            printf(" %d",L[i]);
        printf("\n");
    }
    return 0;
} 

滕雄

2014-11-30                                                               

时间: 2024-11-03 05:30:58

关于拓扑排序的一些想法的相关文章

hiho47 : 拓扑排序&#183;一

时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 由于今天上课的老师讲的特别无聊,小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi,你这学期有选什么课么? 小Hi:挺多的,比如XXX1,XXX2还有XXX3.本来想选YYY2的,但是好像没有先选过YYY1,不能选YYY2. 小Ho:先修课程真是个麻烦的东西呢. 小Hi:没错呢.好多课程都有先修课程,每次选课之前都得先查查有没有先修.教务公布的先修课程记录都是好多年前的,不但有重复的信息,好像很多都不正确了. 小Ho:课程

拓扑排序的原理及事实上现

本文将从下面几个方面介绍拓扑排序: 拓扑排序的定义和前置条件 和离散数学中偏序/全序概念的联系 典型实现算法 Kahn算法 基于DFS的算法 解的唯一性问题 实际样例 取材自下面材料: http://en.wikipedia.org/wiki/Topological_sorting http://en.wikipedia.org/wiki/Hamiltonian_path 定义和前置条件: 定义:将有向图中的顶点以线性方式进行排序.即对于不论什么连接自顶点u到顶点v的有向边uv,在最后的排序结果

拓扑排序的原理及其实现

本文将从以下几个方面介绍拓扑排序: 拓扑排序的定义和前置条件 和离散数学中偏序/全序概念的联系 典型实现算法 Kahn算法 基于DFS的算法 解的唯一性问题 实际例子 取材自以下材料: http://en.wikipedia.org/wiki/Topological_sorting http://en.wikipedia.org/wiki/Hamiltonian_path 定义和前置条件: 定义:将有向图中的顶点以线性方式进行排序.即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,

ACM/ICPC 之 数据结构-邻接表+DP+队列+拓扑排序(TshingHua OJ-旅行商TSP)

做这道题感觉异常激动,因为在下第一次接触拓扑排序啊= =,而且看了看解释,猛然发现此题可以用DP优化,然后一次A掉所有样例,整个人激动坏了,哇咔咔咔咔咔咔咔~ 咔咔~哎呀,笑岔了- -|| 旅行商(TSP) Description Shrek is a postman working in the mountain, whose routine work is sending mail to n villages. Unfortunately, road between villages is

POJ 2367 (裸拓扑排序)

http://poj.org/problem?id=2367 题意:给你n个数,从第一个数到第n个数,每一行的数字代表排在这个行数的后面的数字,直到0. 这是一个特别裸的拓扑排序的一个题目,拓扑排序我也是刚刚才接触,想法还是挺简单的.实现起来也不复杂. 1 #include <stdio.h> 2 #include <string.h> 3 4 int Indegree[101],n; 5 6 bool mp[101][101]; 7 8 int topsort() 9 { 10

[bzoj1565][NOI2009]植物大战僵尸_网络流_拓扑排序

植物大战僵尸 bzoj1565 题目大意:给你一张网格图,上面种着一些植物.你从网格的最右侧开始进攻.每个植物可以对僵尸提供能量或者消耗僵尸的能量.每个植物可以保护一个特定网格内的植物,如果一个植物被保护,那么如果僵尸想吃掉该植物就必须先吃掉保护它的植物.问:僵尸最多能获得多少能量. 注释:1<=N(网格的宽)<=20,1<=M(网格的长)<=30,-20,000<=代价和收益<=20,000. 想法:前置题目([NOI2006]最大获利).这道题和最大获利比较相像,如

拓扑排序讲解

在这里我们要说的拓扑排序是有前提的 我们在这里说的拓扑排序是基于有向无环图的!!!. (⊙o⊙)…我所说的有向无环图都知道是什么东西吧.. 如果不知道,我们下面先来来说说什么是有向无环图. 所谓有向无环图,顾名思义是不存在环的有向图(至于有向图是什么不知道的在前面我们有一个图论讲解上都有). 点的入度:以这个点为结束点的边数. 点的出度:以这个点为出发点的边的条数. 拓扑序就是对于一个节点的一个排列,使得(u,v)属于E,那么u一定出现在v的前面.然而拓扑排序就是一个用来求拓扑序的东西. 对于左

CSU 1804: 有向无环图(拓扑排序)

http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1804 题意:…… 思路:对于某条路径,在遍历到某个点的时候,之前遍历过的点都可以到达它,因此在这个时候对答案的贡献就是∑(a1 + a2 + a3 + ... + ai) * bv,其中a是之前遍历到的点,v是当前遍历的点. 这样想之后就很简单了.类似于前缀和,每次遍历到一个v点,就把a[u]加给a[v],然后像平时的拓扑排序做就行了. 1 #include <bits/stdc++.h>

7-9-有向图无环拓扑排序-图-第7章-《数据结构》课本源码-严蔚敏吴伟民版

课本源码部分 第7章  图 - 有向无环图拓扑排序 ——<数据结构>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑        本源码引入的文件  链接? Status.h.SequenceStack.c.ALGraph.c