拓扑排序详解及其习题

首先说明一下关于图中顶点的度

无向图中顶点的度,指和该顶点相关联的边数。

有向图中顶点的度分为入度和出度,通俗的讲入度就是箭头指向自己(顶点)的边的个数,出度就是箭头背离自己的边的个数。

有向图

b的入度为3 出度为0,c的入度为1 出度为2,d的入度为2 出度为1,a的入度为1 出度为4

无向图

v1点的度为4,v3点的度为2,v1点的度数为4(一个环有两个度)

------->直入主题

拓扑排序:有向图顶点的线性排序就是其拓扑排序。其实可以理解为一个有依赖关系的任务顺序。前提是有向无环图。
例如,图形的顶点可以表示要执行的任务,并且边可以表示一个任务必须在另一个任务之前执行的约束; 在这个应用中,拓扑排序只是一个有效的任务顺序。当且仅当图形没有定向循环,即如果它是有向无环图(DAG),则拓扑排序是可能的。
                                                                                                                                                                    ——来自维基百科

 

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
1、每个顶点出现且只出现一次;
2、若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

易于理解的解释:

比如有人想要制作一件工具,但是这个工具不是一次就可以完成的,分很多个步骤,而且这些步骤是有顺序的,也就是说,假设B的顺序在A的后面,那么你就必须要先完成A再完成B,但是也有些步骤不分顺序,意思是你先做哪一个都是可以的。

比如学习拓扑排序都会提到的这个很火的课程修读关系图:

每个顶点表示学期中的课程,箭头表示学习的先后顺序,有向边( A->D)表示在学习高等代数之前必须要学完代数。我们怎么才能将这些课程处理成一个序列呢?这个序列是我们学习课程,序列保证,箭头左边的点不会出现在箭头右边点的后面。拓扑排序就是处理这样的问题哦

求拓扑排序常用的方法:

(1)从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
(2)从图中删除该顶点和所有以它为起点的有向边。
(3)重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环

 求此图的拓扑排序。

 

 

模板代码:

#include <bits/stdc++.h>
#define Max 505
using namespace std;
vector<int> vec[Max]; //vec 存图
priority_queue<int,vector<int>,greater<int> > q; //优先队列
int in_deg[Max];//入度
int ans[Max];
int n,m;
void topsort()
{
    for(int i=1;i<=n;i++)
    {
        if(in_deg[i]==0)
        {
            q.push(i);
        }
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.top();
        q.pop();
        ans[cnt++]=u;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(!in_deg[vec[u][v]])
            {
                q.push(vec[u][v]);
            }
        }
    } 

    if(cnt!=n) cout<<"-1"<<endl; //环
    else {
        cout<<ans[0];
        for(int i=1;i<cnt;i++)
            cout<<" "<<ans[i];
        cout<<endl;
    }
}

有关拓扑排序的简单题及代码:

HDU-1285

/*
拓扑模板 +优先队列
*/
#include <bits/stdc++.h>
#define Max 505
using namespace std;
vector<int> vec[Max]; //vec 存图
priority_queue<int,vector<int>,greater<int> > q; //优先队列
int in_deg[Max];//入度
int ans[Max];
int n,m;
void init()
{
    memset(in_deg,0,sizeof(in_deg));
    for(int i=0;i<=n;i++)
    {
        vec[i].clear();
    }
    while(!q.empty())
    {
        q.pop();
    }
}
void topsort()
{
    for(int i=1;i<=n;i++)
    {
        if(in_deg[i]==0)
        {
            q.push(i);
        }
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.top();
        q.pop();
        ans[cnt++]=u;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(!in_deg[vec[u][v]])
            {
                q.push(vec[u][v]);
            }
        }
    } 

    if(cnt!=n) cout<<"-1"<<endl; //环
    else {
        cout<<ans[0];
        for(int i=1;i<cnt;i++)
            cout<<" "<<ans[i];
        cout<<endl;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
       init();
       int u,v;
       for(int i=0;i<m;i++)
       {
           cin>>u>>v;
           vec[u].push_back(v); 
           in_deg[v]++;
       }
       topsort();
    }
    return 0;
}

HDU-3342

/*
 HH is 3xian's master and, at the same time, 3xian is HH's master,which is quite illegal! 
 To avoid this,please help us to judge whether their relationship is legal or not. 
判断有没有环
*/
#include <bits/stdc++.h>
#define Max 101
using namespace std;
vector<int> vec[Max];
int in_deg[Max];
queue<int>q;
int n,m;
void init(int nn)
{
    for(int i=0;i<=nn;i++)
    {
        vec[i].clear();
    }
    while(!q.empty()) q.pop();
    memset(in_deg,0,sizeof(in_deg));
}
int topsort()
{
    for(int i=0;i<n;i++)
    {
        if(in_deg[i]==0)
        {
            q.push(i);
        }
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        cnt++;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(!in_deg[vec[u][v]])
            {
                q.push(vec[u][v]);
            }
        }
    }
    /*
    if(cnt==n)
    {
        return 1;//有向无环图
    }else return 0;//非 有向无环图
     */
    return cnt==n;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        if(n==0&&m==0) break;
        init(n);
        int u,v;
        for(int i=0;i<m;i++){
            cin>>u>>v;
            vec[u].push_back(v);
            in_deg[v]++;
        }
        bool ans=topsort();
        if(ans==1) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

HDU-2647

/*
题意:老板给员工发工资 
n个员工,m中关系
每次输入 a,b  
要求:a的工资必须比 b 高
最低工资是 888
求老板一共最少需要付多少工资
如果不能满足所有人的需求的话 就输出-1
*/
#include <bits/stdc++.h>
#define ll long long
#define Max 10002
using namespace std;
int n,m;
int mon[Max];//分层 相当于每个人888工资的加成
int in_deg[Max];
vector<int> vec[Max];
queue<int>q;
void init(int nn)//注意初始化
{
    for(int i=0;i<=nn;i++)
    {
        vec[i].clear();
    }
    memset(in_deg,0,sizeof(in_deg));
    while(!q.empty()) q.pop();
    memset(mon,0,sizeof(mon));
}
int topsort()
{
    for(int i=1;i<=n;i++)
    {
        if(in_deg[i]==0)
        {
            q.push(i);
        }
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        cnt++;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(!in_deg[vec[u][v]])
            {
                q.push(vec[u][v]);
                mon[vec[u][v]]=max(mon[u]+1,mon[vec[u][v]]);
                //money分层
            }
        }
    }
    return cnt==n;
}
int main()
{
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        init(n);
        int u,v;
        for(int i=0;i<m;i++)
        {
            cin>>u>>v;
            vec[v].push_back(u);// 反向建边
            in_deg[u]++;
        }
        ll ans=0;
        if(topsort())
        {
        
            for(int i=1;i<=n;i++)
            {
              //  cout<<i<<": "<<mon[i]<<endl;
                ans+=(888+mon[i]);
            }
            cout<<ans<<endl;
        }else 
        {
            cout<<"-1"<<endl;
        }
    }return 0;
}
/*
4 4 1 2 2 3 1 4 3 4
3 2 1 2 1 3
3 1 1 2

7 7
1 2
2 6
3 2
3 4
4 5
5 6
7 6


*/

POJ-1094

/*
we will give you a set of relations of the form A < B and ask you to determine whether a sorted order has been specified or not.
1、处理到第一个关系的时候能全部确定所有关系 -> Sorted sequence determined after x relations: %&^%.
2、处理到第几个时能发现矛盾(环) -> Inconsistency found after x  relations.
3、没有矛盾但是给出的关系无法确定关系 -> Sorted sequence cannot be determined.

题意就是给你一系列的关系,让你从他给出的关系中从小到大排一个序,如果给出的关系中发生冲突,输出发生冲突的那一步,如果可以得到一个序,输出题目给出到第几个关系才能确定顺序。
注意:之前已经确定了符合约束的序列,那么之后再给出的矛盾的条件是无效的 
eg:--->
5 5
A<B
B<C
C<D
D<E
E<A
output:
Sorted sequence determined after 4 relations: ABCDE
----------------------------------------------------------------------
因为最多只有26个字母,所以怎么写都不会超时。
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
int n,m,cnt;
int in_deg[30];//节点入度数组
int in_degTemp[30];//临时节点入度数组 为了保证加入一个关系后 前后 节点度的统一
char Ans[30];//最后排序的结果
vector<int>vec[30]; //存图
queue<int>q; //队列
void init()
{
    //26个英文字母
    for(int i=0;i<26;i++)
    {
        vec[i].clear();
    }
    while(!q.empty()) q.pop();
    memset(in_deg,0,sizeof(in_deg));
    memset(in_degTemp,0,sizeof(in_degTemp));
    memset(Ans,0,sizeof(Ans));
    
}
int topsort()
{
    //每次输入一个关系要判断一次是否有结果,
    //in_degTemp是 记录每一次加边后-度-改变的-数组  in_deg不能改
    for(int i=0;i<n;i++)
    {
        if(!in_deg[i])
        {
            q.push(i);
        }
    }
    bool unSure=false;//不确定(3)的情况的标记
    cnt=0;
    while(!q.empty())
    {
        //一旦出现有的点没有涉及(比较的关系中),入度为0的点必然大于1,则肯定可以说明 有的点无法确定关系
        if(q.size()>1) unSure=true;
        int u=q.front();
        q.pop();
        Ans[cnt++]=u+'A';
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(!in_deg[vec[u][v]])
            {
                q.push(vec[u][v]);
            }
        }
    }
    if(cnt<n)//存在环
    {
        return 3;
    }
    if(unSure)//不确定
    {
        return 2;
    }
    return 1;// 确定了
}
int main()
{

    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        if(n==0&&m==0) break;
        char op[3];
        bool flag=false;
        int step=0,ans;
        for(int i=0;i<m;i++)
        {
            scanf("%s",op);
            //已经有结果了 就不需要判断后边的关系 但是 还要读入所有数据
            if(flag) continue;
            vec[op[0]-'A'].push_back(op[2]-'A');
            in_deg[op[2]-'A']++;
            memcpy(in_degTemp,in_deg,sizeof(in_deg));
            ans=topsort();//每加一个关系 拓扑处理一次 并且用临时数组存一下,当前入度情况
            memcpy(in_deg,in_degTemp,sizeof(in_degTemp));
            if(ans!=2){
                step=i+1;
                flag=true;
            }//如果不是无结果的话 那么就是 回路或者有结果
        }
        if(ans==1){
            Ans[cnt]='\0';
            printf("Sorted sequence determined after %d relations: %s.\n",step,Ans);
        }else if(ans==2)
        {
            printf("Sorted sequence cannot be determined.\n");
        }else if(ans==3)
        {
            printf("Inconsistency found after %d relations.\n", step);
        }
    }
    return 0;
}

HDU-4857

/*
注意此题不是保证字典序,而是要最小的尽量在前面。
对于有m组限制即a必须在b前面出去,而对于那些没有限制的就必须按照标号从小到大出去,也就是说假设有三个人,3号必须在1号前面除去,2号没限制,那么出去的顺序为3 1 2,为了满足这个需求,反向建图+拓扑排序+优先队列+逆序输出
*/
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#define ll long long
#define Max 30002
using namespace std;
int n,m,a,b;
int in_deg[Max],ans[Max];
vector<int> vec[Max];
priority_queue<int,vector<int>,less<int> > q;//从大到小
//priority_queue<Type, Container, Functional>
void init()
{
    while(!q.empty()) q.pop();
    memset(in_deg,0,sizeof(in_deg));
    memset(ans,0,sizeof(ans));
    for(int i=1;i<=n;i++){
        vec[i].clear();
    }
}
void toposort()
{
    for(int i=1;i<=n;i++)
    {
        if(in_deg[i]==0)
        {
            q.push(i);
        }
    }
    int cnt=0;
    while(!q.empty())
    {
        int u=q.top();q.pop();
        ans[cnt++]=u;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(in_deg[vec[u][v]]==0)
            {
                q.push(vec[u][v]);
            }
        }
    }
    if(cnt!=n) {
       // cout<<"-1"<<endl;
        printf("-1\n");
    }else
    {
       // cout<<ans[0];
        printf("%d",ans[cnt-1]);
        for(int i=cnt-2;i>=0;i--)
            printf(" %d",ans[i]);
           // cout<<" "<<ans[i];
        printf("\n");
    }
    
}
int main()
{
    // ios::sync_with_stdio(false);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        //cin>>n>>m;
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)
        {
            //cin>>a>>b;
            scanf("%d%d",&a,&b);
            vec[b].push_back(a);//反向建图
            //vec[a].push_back(b);
            in_deg[a]++;
        }
        toposort();
    }
    return 0;
}
/*
1
3 1
3 1
------
3 1 2
*/
/*
在主要的拓扑排序的基础上又添加了一个要求:编号最小的节点要尽量排在前面;在满足上一个条件的基础上,编号第二小的节点要尽量排在前面;在满足前两个条件的基础上,编号第三小的节点要尽量排在前面……依此类推。(注意,这和字典序是两回事,不能够混淆。)
*/

/*
是反向建边,点大的优先级高,用拓扑排序+优先队列,逆向输出序列即可。

根据每对限制,可确定拓扑序列,但此时的拓扑序列可能有多个(没有之间关系的点的顺序不定)。本题要求较小的点排到前面,则可确定序列。

(1)如果点a和点b有直接和简接的拓扑关系,那么a和b的先后顺序可有拓扑排序确定。

(2)如果点a和点b没有直接和简接的拓扑关系,那么a和b的先后顺序由a和b所能到达的点的确定。

如:

1

3 2

3 1

3 1

应输出结果为 3 1 2

点3 和 点2 没有直接的拓扑关系,但是3到达最小点为1,2到达最小点为2。

综合(1)和(2)本题需要逆向处理。

PS:欧拉回路的路径输出也是逆向输出的。

*/

POJ-3687

#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstdio>
#define ll long long
#define Max 202
using namespace std;
int n,m,a,b;
int in_deg[Max],ans[Max];
vector<int> vec[Max];
priority_queue<int,vector<int>,less<int> > q;//从大到小
void init()
{
    memset(ans,0,sizeof(ans));
    memset(in_deg,0,sizeof(in_deg));
    for(int i=1;i<=n;i++)
        vec[i].clear();
    while(!q.empty()) q.pop();
}
void toposort()
{
    for(int i=1;i<=n;i++)
    {
        if(in_deg[i]==0) q.push(i);
    }
    int cnt=0;
    int weigh=n;
    while(!q.empty()){
        int u=q.top(); q.pop();
        cnt++;
        ans[u]=weigh--;
        for(int v=0;v<vec[u].size();v++)
        {
            in_deg[vec[u][v]]--;
            if(in_deg[vec[u][v]]==0)
            {
                q.push(vec[u][v]);
            }
        }
    }
    if(cnt!=n)
        printf("-1\n");
    else
    {
        printf("%d",ans[1]);
        for(int i=2;i<=cnt;i++)
            printf(" %d",ans[i]);
        printf("\n");
    }
    
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            vec[b].push_back(a);
            in_deg[a]++;
        }
        toposort();
    }
    return 0;
}
/*
有n个球,分别编号为1到n,并且每个球有对应的重量,输入时,每行输入的两个数,表示“编号”为a的球的重量小于“编号为b的球的重量”,分别输出1到n编号对应的球的重量,尽量保证编号较小的球重量也较小。注意仔细理解题:读入的时候是球的编号,输出的是对应编号的球的重量,看到dicuss区的数据才发现自己题目都读错了。
*/

/*
3

5 4
5 1
4 2
1 3
2 3

10 5
4 1
8 1
7 8
4 1
2 8


5 4
1 4
4 2
5 3
3 2
ans:
2 4 5 3 1        逆向建图
5 1 6 2 7 8 3 4 9 10  重边的话就输出 -1

标签小的重量尽量小

1~n编号的盒子,放1~n重量的球,给出m对盒子装的小球之间大小的关系,输出1~n编号盒子重量,要求编号越小的盒子,放的重量越小。
*/

END,BYEBYE!

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值