拓扑排序及相关整理

什么是拓扑序列
在图论中,拓扑排序(Topological Sorting)是一个有向无环图DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
1:每个顶点出现且只出现一次。
2:若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

AOV网
拓扑排序对应施工的流程图具有特别重要的作用,它可以决定哪些子工程必须要先执行,哪些子工程要在某些工程执行后才可以执行。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。

主要实现步骤
1:建立有向边,记录终点边的入度
2:首先将入度为0的加入队列中
3:队列循环,删除入度为0的点及其依附的边,同时更新依附点入度,若为0加入队列中

#include<bits/stdc++.h>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define pii pair<int,int>
using namespace std;
const int maxn=3e4+5;

/**复杂度 O(V+E) 点数+边数**/
queue<int>q;
int indx[maxn];/**入度**/
vector<int>V;/**拓扑序列**/
vector<int>edge[maxn];/**邻接表**/

int main()
{
    int n,m,u,v;
    mem(indx,0);V.clear();
    while(!q.empty()){q.pop();}
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        indx[v]++;
        edge[u].push_back(v);
    }

    for(int i=1;i<=n;i++){
        if(indx[i]==0){q.push(i);}
    }
    while(!q.empty()){
        int pp=q.front();q.pop();
        V.push_back(pp);
        for(int i=0;i<edge[pp].size();i++){
            int kk=edge[pp][i];
            indx[kk]--;
            if(indx==0){
                q.push(kk);
            }
        }
    }
    if(V.size()==n){
        cout<<"YES"<<endl;
    }
    else{
        cout<<"存在环路"<<endl;
    }
    return 0;
}

相关例题:

hdu1811

思路:
1:对于’ >’ 和 ‘<’ 我们可以很好的处理,但 ‘=’ 该如何处理呢,为了减少相等之间的累赘建边,我们可以考虑将 ‘=’ 的集合分别用集合中一个根来代替,用并查集维护。
2:进行拓扑排序,若存在回路说明信息冲突;若拓扑序列存在,当且仅当每次队列中只有一个元素是才可以确定唯一的序列,即OK,反之是一个多种可能排列的拓扑序列即信息不全。

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=1e4+5;

int indx[maxn],f[maxn];
queue<int>q;
vector<int>edge[maxn];
struct node
{
    int x,y;
    char ch;
}p[maxn<<1];

void init(int n)
{
    for(int i=0;i<n;i++){
        edge[i].clear();indx[i]=0;f[i]=i;
    }
    while(!q.empty()){q.pop();}
}
int getfind(int x)
{
    return x==f[x]?x:f[x]=getfind(f[x]);
}

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
        init(n);
        for(int i=1;i<=m;i++){
            scanf("%d %c %d",&p[i].x,&p[i].ch,&p[i].y);
            if(p[i].ch=='='){/**将相等的集合归类**/
                int x=getfind(p[i].x);
                int y=getfind(p[i].y);
                if(x!=y){f[y]=x;}
            }
        }
        bool ans=false;
        for(int i=1;i<=m;i++){
            if(p[i].ch=='='){continue;}
            int x=getfind(p[i].x),y=getfind(p[i].y);
            if(x==y){/**前文出现过 '=' **/
                ans=true;break;
            }
            if(p[i].ch=='>'){
                edge[x].push_back(y);indx[y]++;
            }
            else{
                edge[y].push_back(x);indx[x]++;
            }
        }
        if(ans){cout<<"CONFLICT"<<endl;continue;}
        int num=0;
        for(int i=0;i<n;i++){
            if(i==getfind(i)){
                num++;/**实际存在的根数**/
                if(!indx[i]){q.push(i);}
            }
        }
        bool flag=true;
        while(!q.empty()){
            if(q.size()>1){flag=false;}/**每次队列中仅存1个元素时才能确定唯一序列**/
            int pp=q.front();q.pop();
            num--;
            for(int i=0;i<edge[pp].size();i++){
                int tt=edge[pp][i];
                indx[tt]--;
                if(indx[tt]==0){q.push(tt);}
            }
        }
        if(num>0){cout<<"CONFLICT"<<endl;}
        else if(!flag){cout<<"UNCERTAIN"<<endl;}
        else if(flag){cout<<"OK"<<endl;}
    }
    return 0;
}

牛客:可达性统计

思路:
1:非常巧妙的应用了 STL bitset 容器
2:看到DAG应该较快想到拓扑排序;对当前点考虑,它能到达的点为 (1):直接与它相连; (2):与相连点相连的点;进行拓扑排序得到序列。
3:对序列分析:靠后的点若与前面点相连,那么它能到的点必然可以更新到前面点上。使用bitset进行状态记录,状态转移时直接异或即可。

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=1e4+5;

int indx[maxn],ans[maxn];
queue<int>q;
vector<int>edge[maxn],v;
bitset<maxn> f[maxn];

int main()
{
    mem(indx,0);
    int n,m,x,y;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        indx[y]++;
        edge[x].push_back(y);
    }
    for(int i=1;i<=n;i++){
        if(!indx[i]){q.push(i);}
    }
    while(!q.empty()){
        int pp=q.front();q.pop();
        v.push_back(pp);
        for(int i=0;i<edge[pp].size();i++){
            int tt=edge[pp][i];
            if(--indx[tt]==0){q.push(tt);}
        }
    }
    for(int i=v.size()-1;i>=0;i--){
        int kk=v[i];
        f[kk].reset();f[kk][kk]=1/** 1 代表可达点 **/
        for(int j=0;j<edge[kk].size();j++){
            f[kk]|=f[edge[kk][j]];/**状态转移**/
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d\n",f[i].count());
    }
    return 0;
}

hdu2647

思路:反向建边,由低工资者指向高工资者。拓扑排序若存在环输出 “-1” ,反之将图转化为层的形式。详情见代码

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int maxn=1e4+5;

int indx[maxn],ans[maxn];
queue<int>q;
vector<int>edge[maxn],v;

int main()
{
    int n,m,x,y;
    while(scanf("%d%d",&n,&m)!=EOF){
    for(int i=1;i<=n;i++){
        indx[i]=0;ans[i]=0;
        edge[i].clear();
    }
    while(!q.empty()){q.pop();}
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        indx[x]++;
        edge[y].push_back(x);
    }
    for(int i=1;i<=n;i++){
        if(!indx[i]){q.push(i);}
    }
    int num=n;
    while(!q.empty()){
        int pp=q.front();q.pop();
        num--;
        for(int i=0;i<edge[pp].size();i++){
            int tt=edge[pp][i];
            if(--indx[tt]==0){q.push(tt);}
            ans[tt]=max(ans[pp]+1,ans[tt]);/**更新该位置最低工资,不懂就模拟**/
        }
    }
    if(num>0){cout<<"-1"<<endl;continue;}
    int ant=0;
    for(int i=1;i<=n;i++){
        ant+=(888+ans[i]);
    }
    cout<<ant<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值