拓扑排序简介+hdu4857逃生(逆向建图+拓扑排序)

3 篇文章 0 订阅
1 篇文章 0 订阅

拓扑排序

简介

  • 将有向图中的顶点以线性的方式排序,在任何连接自顶点u到v的有向边uv,在最后排序的结果中u总在v之前。(有向无环图)

思想

  1. 从有向图中选取一个入度为0的顶点,输出。
  2. 删除该顶点,包括关联的所有边。
  3. 直到图空或者找不到入度为0的点。
  4. 每次的选择只能是唯一或者不确定,不能有矛盾的情况。

模板

    #include <stdio.h>
    #include <set>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #define maxn 30010
    using namespace std;

    int n,m;  
    int indegree[ maxn ];
    vector< vector<int> > G(maxn);       //存储关系 
    set<int> s;             
    int ans[maxn];                       //最终的顺序 

    void init(int n)
    {
        memset(indegree,0,sizeof(0));
        s.clear();
        for(int i = 1; i <= n ; i++)
            G[i].clear();
        memset(ans,0,sizeof(ans));
    }

    int toposort()
    {
        int i,pos=0;

        //按照题目应该先加入最大的 
        for(i = n; i >= 1 ;i--)
            if(indegree[ i ] == 0)
                s.insert( i );

        while ( !s.empty() )
        {
            i = *s.begin();
            ans[++pos]=i;
            s.erase( s.begin() );

            //删除掉这个点,并且把所有连接边删除,其他点的入度也要改变 
            while ( !G[ i ].empty() )
            {
                //G[i]的出边的点的入度减1 
                indegree[G[i].back()] --;

                //入度为0了 加入set 
                if(indegree[G[i].back()] == 0)
                    s.insert(G[i].back());

                G[i].pop_back(); 
            }
        }

        if (pos==n)
            return 1;
        else
            return 0;
    }
    int main()
    {
        int T,i;  
        scanf( "%d", &T );
        while ( T-- )
        {      
            scanf("%d%d",&n,&m);
            init(n);

            //读入关系 
            while(m--) 
            {
                int i,j;
                scanf("%d%d",&i, &j);

                //不重复加载 
                if(find(G[i].begin(),G[i].end(),j) == G[i].end())
                {
                    G[i].push_back(j);
                    indegree[j]++;  
                }    
            }   

            //拓扑排序 
            toposort();   

            for (i=1;i<=n;i++)
                printf("%d ",ans[i]);
            printf("\n");
        }

        return 0;
    }  


例题(逆向建图+拓扑排序)

hdu4857逃生

思路:

  • 如果按照普通的拓扑排序,正向建图,每次选取入度为0的最小的点(小根堆),那么看似可以,但是题目要我们尽量把题号最小的放到前面输出。那么也就是说5->4->13->2,我们也应该是输出5->4->1后,再考虑2和3,因为1的优先级太高了,我们想尽可能快得输出1,所以就要把1之前的统统先输出。

  • 那么就不能按照通常的方法。我们考虑逆向建图,那么在有多个入度为0的顶点时,我们选择数字大的点。为什么?假设现在是3和5的入度为0,那我们肯定想的是,最终结果中3要最先输出,那么我们现在就要把5放到3后面输出,因为后面我们会逆序输出,所以现在就让5加入大根堆,同时删除与5关联的边。那么这样每次选择大的,放到前面,那么之后逆序肯定是在后面。

AC代码:


    #include <stdio.h>
    #include <set>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #define maxn 30010
    using namespace std;

    int n,m;  
    int indegree[ maxn ];
    vector< vector<int> > G(maxn);       //存储关系 
    set<int,greater<int> > s;            //按照题目应该是大根堆 
    int ans[maxn];                       //最终的顺序 

    void init(int n)
    {
        memset(indegree,0,sizeof(0));
        s.clear();
        for(int i = 1; i <= n ; i++)
            G[i].clear();
        memset(ans,0,sizeof(ans));
    }

    int toposort()
    {
        int i,pos=0;

        for(i = 1; i <= n ;i++)
            if(indegree[ i ] == 0)
                s.insert( i );

        while ( !s.empty() )
        {
            i = *s.begin();
            ans[++pos]=i;
            s.erase( s.begin() );

            //删除掉这个点,并且把所有连接边删除,其他点的入度也要改变 
            while ( !G[ i ].empty() )
            {
                //G[i]的出边的点的入度减1 
                indegree[G[i].back()] --;

                //入度为0了 加入set 
                if(indegree[G[i].back()] == 0)
                    s.insert(G[i].back());

                G[i].pop_back(); 
            }
        }

        if (pos==n)
            return 1;
        else
            return 0;
    }
    int main()
    {
        int T,i;  
        scanf( "%d", &T );
        while ( T-- )
        {      
            scanf("%d%d",&n,&m);
            init(n);

            //读入关系 
            while(m--) 
            {
                int i,j;
                scanf("%d%d",&i, &j);

                //不重复加载 逆向建图 
                if(find(G[j].begin(),G[j].end(),i) == G[j].end())
                {
                    G[j].push_back(i);
                    indegree[i]++;  
                }    
            }   

            //拓扑排序 
            toposort();   

            //逆向输出 
            for (i=n;i >= 1;i--)
            {
                if(i == 1)
                    printf("%d\n",ans[i]);
                else 
                    printf("%d ",ans[i]);
            }
        }

        return 0;
    }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值