• 拓扑排序简介


     

    1.  首先来说拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。

       该序列必须满足下面两个条件:

         第一:每个顶点出现且只出现一次。

         第二:若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。  

       当然有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
        例如,右边这个图:

        它是一个 DAG 图,那么如何写出它的拓扑排序呢?

    2.  怎么求拓扑排序。

       思路一:从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。从图中删除该顶点和所有以它为起点的有向边。

       重复这两个动作直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

       于是,上图得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。

       但是要注意:一个图的拓扑排序得到的结果可能存在多个

       这次算法也就是我们经常说到的Kahn算法。

       给个例题吧:UVA - 10305 

       

       利用我们的Kahn算法来写就是如下:

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <string.h>
    #include <algorithm>
    #include <math.h>
    #include <vector>
    #include <queue>
    using namespace std;
    typedef long long LL;
    const int maxn=100005;
    int n,m;
    int a,b;
    vector<int>v[maxn];
    int indrgee[maxn];
    int order[maxn];
    queue<int>q;
    
    void toposort()
    {
        for(int i=1;i<=n;i++)//找到初始情况下入度为0的点
        {
            if(indrgee[i]==0)
                q.push(i);
        }
        int cnt=0;
        while(!q.empty())
        {
            int temp=q.front();
            q.pop();
            order[cnt++]=temp;
            int len=v[temp].size();
            for(int i=0;i<len;i++)
            {
                indrgee[v[temp][i]]--;
    //每次删去该顶点和所有以它为起点的有向边故对应点的入度减一,如果为0,就放入队列
                if(indrgee[v[temp][i]]==0)
                    q.push(v[temp][i]);
            }
        }
        printf("%d",order[0]);
        for(int i=1;i<cnt;i++)
            printf(" %d",order[i]);
        printf("
    ");
    }
    int main()
    {
        while(scanf("%d %d",&n,&m)!=EOF)
        {
            if(n==0&&m==0)
                break;
            while(!q.empty())
                q.pop();
            for(int i=0;i<=n;i++)
            {
                v[i].clear();
                indrgee[i]=0;
            }
            while(m--)
            {
                scanf("%d %d",&a,&b);
                v[a].push_back(b);
                indrgee[b]++;//对应的入度加1.
            }
            toposort();
        }
        return 0;
    }

       思路二:

      首先我们讨论一下拓扑排序的性质,对于一个图,它可能会有好几种拓扑排序,但他们同时满足一个规律,那就是如果存在有向边u->v, 那么结点u必须排在v之前(前驱)。同时这种性质具有传递性,也就是说如果同时存在v->t, 那么满足ut之前。同样的,如果uv两个结点在图中并不满足这种性质,那么谁在前谁在后就无所谓了。正是利用这个规则,我们进行dfs的顺序是无所谓的。

    为何?因为我们从root结点开始dfs一遍,可以找到所有的必须在这个root结点之后的点,那么我们就满足了拓扑序的规则了,那么我们无论先dfs(u)还是先dfs(v), 都不会违背这个规则(除非有环),那么同时我们只要按照某种合理的方式存储所有这些点,那么他们就是拓扑序了。

    什么是合理的方式?栈!考量一个dfs(u), 在它结束该退出时,它代表它的结点u。在dfs递归中,什么点会最先exit?没有后继结点的点(或者后继已入栈的点)!那么把所有点分成两个集合,一个是待处理的点集D,一个是已拓扑排序后的点集A,当且仅当D中某个点没有后继结点(或该后继结点已经加入了点集A中)时,它可以从D转移到A,而dfs的回溯方式,恰恰就自动实现了这样的功能。

      那么上面的那个例题用dfs来写的话就是

      

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <string.h>
    #include <algorithm>
    #include <math.h>
    #include <vector>
    #include <queue>
    using namespace std;
    typedef long long LL;
    const int maxn=105;
    int n,m;
    int a,b;
    bool G[maxn][maxn];
    int vis[maxn];
    int order[maxn];//代替栈,每次插入插到拓扑排序首位
    int len;
    //用vis数组,vis[u]=0表示从来没访问过,vis[u]=1表示已经访问过了,vis[u]=-1表示
    //正在访问,即递归调用还在进行,尚未返回。
    bool dfs(int u)
    {
        vis[u]=-1;
        for(int v=1;v<=n;v++)
        {
            if(G[u][v])
            {
                if(vis[v]<0)
                    return false;
                else if(!vis[v]&&!dfs(v))
                    return false;
            }
        }
        vis[u]=1;
        order[--len]=u;
        return true;
    }
    bool toposort()
    {
        len=n;
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++)
        {
            if(!vis[i])//如果没有被访问过
            {
                if(!dfs(i))//如果没有还
                    return false;
            }
        }
        return true;
    }
    int main()
    {
        while(scanf("%d %d",&n,&m)!=EOF)
        {
            if(n==0&&m==0)
                break;
            memset(G,0,sizeof(G));
            while(m--)
            {
                scanf("%d %d",&a,&b);
                G[a][b]=true;
            }
            toposort();
            printf("%d",order[0]);
            for(int i=1;i<n;i++)
                printf(" %d",order[i]);
            printf("
    ");
        }
        return 0;
    }
  • 相关阅读:
    晕,又要学新东西了!
    十一之旅(1)
    结束放假◎!
    容颜总有一天会慢慢老去
    JS里在光标位置插入字符
    放假啦,暂别七天
    好久没来,小小的Happy一下
    唉唉唉
    关于Timer使用,为什么程序会死掉
    于Excel文件上传读取数据的问题
  • 原文地址:https://www.cnblogs.com/jkzr/p/9755840.html
Copyright © 2020-2023  润新知