拓扑排序

拓扑排序:

定义:

在图论中,拓扑排序(Topological Sorting)是求一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:

        1:每个顶点出现且只出现一次。
        2: 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
例如:

这个图的一个拓扑排序序列为:1-->2-->4-->3-->5

求拓扑序列的算法:

KAHN算法。

算法思想:

每次都找图中入度为零的点,然后将该点从图中删除,然后重复上述操作,直到图中没有顶点就结束。

算法流程:

用栈来实现这个算法。

步骤一:选取所有入度为零的点放入栈中去。

步骤二:从栈顶开始弹出顶点,每弹出一个顶点,将该顶点从图中删除。

步骤三:将新得到的图中的入度为零的点放入栈中。

步骤四:重复步骤二和步骤三,直到图中没有顶点了就结束算法。

通过拓扑排序,我们还可以判断图中是否存在环,具体操作看代码。

代码:

#include<bits/stdc++.h>
#define MAXN 9999
using namespace std;
int e[MAXN][MAXN];//邻接矩阵
int u,v;//顶点--->终点  无权值
int n,m;//n个顶点,m条边
int enter[MAXN];//enter[i]代表编号为i的顶点的入度是多少
int jieguo[MAXN];//存放一条拓扑排序的路径
int coun=0;//jieguo数组中顶点的个数,用于判断图中是否有环
int Kahn()
{
    stack < int > q;//定义一个栈
    for(int i=1;i<=n;i++)//遍历所有顶点,将所有入度为0的顶点都放入栈中去
        if(!enter[i])
            q.push(i);
    while (!q.empty())
    {
        int z=q.top();//弹出栈顶元素,将该点在图中删除
        q.pop();
        jieguo[coun++]=z;//存储这个点
        for(int i=1;i<=n;i++)//在图中删除这个点,所谓删除,就是将该顶点的所有出边点的入度减一
            if(e[z][i])
            {
                 enter[i]--;
                 if(!enter[i])//如果该顶点的出边点的入度减一后变成了0,那么将该出边点放入到栈中去
                    q.push(i);
            }
    }
    if(coun<n)
    {
        cout<<"该图中存在环!"<<endl;
        return 0;
    }
    return 1;
}
int main()
{
    cin>>n>>m;
    memset(enter,0,sizeof(enter));
    memset(jieguo,0,sizeof(jieguo));
    memset(e,0,sizeof(e));
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v;
        e[u][v]=1;
        enter[v]++;//初始化enter数组
    }
    if(Kahn()==1)
    {
        for(int i=0;i<coun-1;i++)
            cout<<jieguo[i]<<"-->";
        cout<<jieguo[coun-1];
    }
}

例题:

例题一:【POJ-1094】

http://poj.org/problem?id=1094

题意:

给你一些大写字母间的偏序关系,然后让你判断能否唯一确定它们之间的大小关系,或者所给关系是矛盾的,或者到最后也不能确定它们之间的关系。如果能够确定它们之间的大小关系,就输出是在第几个偏序关系时确定的它们之间的大小关系,如果所给的关系是矛盾的,就输出是在第几个偏序关系时确定的它们之间的关系是矛盾的。

思路:

拓扑排序,因为题目要输出的是在第几组偏序关系时确定的它们之间的关系的,所以对于每次输入的偏序关系,我们都要进行一次拓扑排序,拓扑排序函数进行完成后返回的结果总共有三种:

第一种:得到了拓扑序列。

第二种:出现了矛盾(图中存在环)。

第三种:条件不足,无法确定它们之间的关系或者出现了矛盾。

只要拓扑排序函数返回的是第一二种情况,我们直接输出结果就可以了,对于后面输入的偏序关系就不用管了,如果拓扑排序函数返回的一直是第三种结果,那么我们还要对后面输入的偏序关系继续进行拓扑排序,如果所有的偏序关系都输入完成了,拓扑排序函数返回的还是第三种结果,说明我们到最后也不能确定它们之间的关系。

代码:

#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 100
typedef long long ll;
using namespace std;
int e[MAXN][MAXN];//邻接矩阵
int JieGuo[MAXN];//保存路径的结果数组
int enter[MAXN];//入度数组
int j=0;
int n,m;
int Count=0;//记录进行拓扑排序的次数
char a,b,c;
int Kahn()
{
    int Enter[MAXN];
    for(int i=1;i<=n;i++)//不能用原enter数组,必须重新生成一个数组,因为后面的操作会改变数组中的值,如果用原enter数组,那么各个顶点的入度就乱了
        Enter[i]=enter[i];
    int ret=1;
    stack < int > q;//定义一个栈
    for(int i=1;i<=n;i++)//遍历所有顶点,将所有入度为0的顶点都放入栈中去
        if(!Enter[i])
            q.push(i);
    while (!q.empty())
    {
        if(q.size()>1)//只要有2个或2个以上入度为0的点,说明当前无法确定它们之间的顺序
            ret=0;
        int z=q.top();//弹出栈顶元素,将该点在图中删除
        q.pop();
        JieGuo[j++]=z;//存储这个点
        for(int i=1;i<=n;i++)//在图中删除这个点,所谓删除,就是将该顶点的所有出边点的入度减一
            if(e[z][i])
            {
                 Enter[i]--;
                 if(!Enter[i])//1如果该顶点的出边点的入度减一后变成了0,那么将该出边点放入到栈中去
                    q.push(i);

            }
    }
    if(j<n)//出现了矛盾(图中出现了环)
        return -1;//返回-1
    return ret;//要么返回0,要么返回1,返回0代表无法确定它们之间的关系,返回1代表它们之间的关系可以确定了
}
int main()
{
    while(1)
    {
        cin>>n>>m;
        if(n==0&&m==0)
            break;
        memset(e,0,sizeof(e));
        memset(enter,0,sizeof(enter));
        Count=0;
        int flag=0;
        for(int i=0;i<m;i++)
        {
            cin>>a>>b>>c;
            if(flag)
                continue ;
            e[a-'A'+1][c-'A'+1]=1;
            enter[c-'A'+1]++;
            memset(JieGuo,0,sizeof(JieGuo));
            j=0;
            Count++;
            flag=Kahn();//每输入一个偏序关系,如果还没有确定它们之间的关系或者还没有推导出矛盾来,就继续执行拓扑排序
            if(flag==1)//确定出他们之间的关系了
            {
                cout<<"Sorted sequence determined after "<<Count<<" relations: ";//输出是在第几个偏序关系时确定的
                for(int i=0;i<j;i++)
                {
                    char c=JieGuo[i]+'A'-1;
                    cout<<c;
                }
                cout<<'.'<<endl;
            }
            else if(flag==-1)//有矛盾
                cout<<"Inconsistency found after "<<Count<<" relations."<<endl;
        }
        if(!flag)//到最后了也无法确定它们之间的关系
            cout<<"Sorted sequence cannot be determined."<<endl;
    }
}

例题二:【HDU-1285】

http://acm.hdu.edu.cn/showproblem.php?pid=1285

中文题。

思路:

运用拓扑排序,但是题目要求:

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前

所以我们用优先级队列来代替栈结构,确保在正确输出时对于无法确定排名的队伍编号小的在前,编号大的在后。还有就是题目的输入数据可能存在重边,需要判断一下。这个题也就这一个需要注意的地方,其余都很简单。

代码:

#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 1000
typedef long long ll;
using namespace std;
int e[MAXN][MAXN];
int JieGuo[MAXN];
int enter[MAXN];
int a,b;
int n,m;
int conn=0;
void Kahn()
{
    priority_queue<int,vector<int>,greater<int> >q;//优先级队列,小的先出队
    for(int i=1;i<=n;i++)
        if(!enter[i])
            q.push(i);
    while(!q.empty())
    {
        int z=q.top();
        q.pop();
        JieGuo[conn++]=z;
        for(int i=1;i<=n;i++)
        {
            if(e[z][i])
            {
                enter[i]--;
                if(!enter[i])
                    q.push(i);
            }
        }
    }
    return ;
}
int main()
{
    while(cin>>n>>m)
    {
        memset(e,0,sizeof(e));
        memset(enter,0,sizeof(enter));
        conn=0;
        while(m--)
        {
            cin>>a>>b;
            if(e[a][b])//题目可能存在重边,需要判断一下
                continue;
            e[a][b]=1;
            enter[b]++;
        }
        Kahn();
        for(int i=0;i<conn-1;i++)
            cout<<JieGuo[i]<<" ";
        cout<<JieGuo[conn-1]<<endl;

    }
}

例题三【HDU-3342】:

题目:

给你一个图,让你判断图中是否有环

思路:

直接套模板,注意有重边

代码:

#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 300
typedef long long ll;
using namespace std;
int n,m;
int u,v;
int Size=0;
int e[MAXN][MAXN];
int degree[MAXN];
bool Kahn()
{
    queue < int >q;
    for(int i=0;i<n;i++)
        if(!degree[i])
            q.push(i);
    while(!q.empty())
    {
        int z=q.front();
        q.pop();
        Size++;
        for(int i=0;i<n;i++)
        {
            if(e[z][i])
            {
                degree[i]--;
                if(!degree[i])
                    q.push(i);
            }
        }
    }
    if(Size<n)
        return false;
    return true ;
}
int main()
{
    while (cin>>n>>m)
    {
        memset(e,0,sizeof(e));
        memset(degree,0,sizeof(degree));
        Size=0;
        if(m==0)
            break;
        while (m--)
        {
            cin>>u>>v;
            if(e[u][v])//题目可能存在重边,需要判断一下
                continue;
            e[u][v]=1;
            degree[v]++;
        }
        if(Kahn())
            cout<<"YES"<<endl;
        else
            cout<<"NO"<<endl;
    }
}

例题四【HDU-2647】:

http://acm.hdu.edu.cn/showproblem.php?pid=2647

题目:

老板要给很多员工发奖金, 但是部分员工有个虚伪心态, 认为自己的奖金必须比某些人高才心理平衡; 但是老板很人道, 想满足所有人的要求, 并且很吝啬,想花的钱最少

输入若干个关系

a b

a c

c b

意味着a 的工资必须比b的工资高 同时a 的工资比c高; c的工资比b高。

如果老板能够满足所有员工的要求,就输出老板需要花的最少的钱,如果不能够满足,就输出-1

思路:

拓扑排序

我们先抽象出图来,我们假设a和b之间有一条有向边b-->a,代表b的工资比a少,由于题目输入(a b)代表a的工资必须比b高,那么就是从b到a之间有一条有向边,b-->a,所以我们必须建立反向边。

我们定义数组ceng[i]代表节点i在拓扑序列的第几层,

通过邻接表来存图,顺便学一下如何在邻接表上建立反向图。

代码:

#include<algorithm>
#include<iostream>
#include<limits.h>
#include <sstream>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#define mod 1000000007
#define MAXN 20005
typedef long long ll;
using namespace std;
int n,m;
int u[MAXN],v[MAXN];
int Size=0;
int first[10005],next1[MAXN];
int ceng[10005];
int enter[10005];
int Kahn()
{
    queue < int >q;
    for(int i=1;i<=n;i++)
        if(!enter[i])
            q.push(i);
    while (!q.empty())
    {
        int z=q.front();
        q.pop();
        Size++;
        int p=first[z];
        while(p!=-1)
        {
            enter[u[p]]--;
            if(!enter[u[p]])
            {
                q.push(u[p]);
                ceng[u[p]]=max(ceng[u[p]],ceng[v[p]]+1);//这个地方不太会
            }
            p=next1[p];
        }
    }
    if(Size!=n)
        return -1;
    int sum=0;
    for(int i=1;i<=n;i++)
        sum+=888+ceng[i];
    return sum;
}
void fuzhi(int u,int v,int i)//在邻接表上建立反向图
{
    next1[i]=first[v];
    first[v]=i;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(u,0,sizeof(u));
        memset(v,0,sizeof(v));
        memset(ceng,0,sizeof(ceng));
        memset(enter,0,sizeof(enter));
        memset(first,-1,sizeof(first));
        memset(next1,0,sizeof(next1));
        Size=0;
        for(int i=1;i<=m;i++)
        {
            cin>>u[i]>>v[i];
            fuzhi(u[i],v[i],i);
            enter[u[i]]++;
        }
        cout<<Kahn()<<endl;
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值