浅谈拓扑排序

##1. 什么是拓扑排序?

拓扑排序就是在一个有向无环图(也称DAG图),将所有的点进行排序,使得每条有向边的起点u都排在终点v的前面。

如果您觉得上述比较难理解,可以举一个简单的例子。比如,在洛谷中的试炼场,有一些有先决要求的任务,这些任务必须要先完成其他的任务才能做,因此要先完成以前的子任务才能去做,有先解决任务的优先级要比它的子任务要高,这种关系可以用一个有向图来表示。

假设一个有先决要求的任务为v,必须先要完成任务u,这时就可以建一条有向边,以u为起点,v为终点。通常,我们把这种点表示任务,边表示任务之间的前后顺序关系的有向图称为AOV网(Activity On Vertex network)。

##2.如何实现拓扑排序?

实现拓扑排序这里介绍两种算法,一种是DFS(深度优先搜索)和BFS广度优先搜索。

我们先来谈谈如何用BFS的方法。

① 先建图。这里本蒟蒻采用的是邻接表方法。用C++STL中的vector(当然也可用链式前向星)。读入两个点u,v。u是起点,v是终点,将v插入u的vector中,即G[u].push_back(v);这样第一步就做好了qwq

② BFS实现拓扑排序。首先我们找图中所有入度为0的点。(入度就是一个点被指向边的数量,一个有向无环图中一定存在一个入度为0的点)。那怎样才能统计入度?很简单,只要在刚才建图的过程中,把每条边终点v的入度加1即可,把每个点的入度用一个一维数组来存储。如果您不是很理解,我们不妨先画一个图。

lulinrui666

以上这幅图中入度为0的点为1和6(没有边指向1与6),我们就把1号点和6号点放入一个队列当中。学过BFS的同学都知道,一般写BFS的时候都要用到队列(数据结构队列在这里作者不在阐述,不知道的话可以自行百度)。

然后我们拿出队列中的首项v,这是就可以放心大胆地输出这个点的序号了,因为现在的图中不存在一条边的终点是v,比它优先级高的点要么已经输出过了,要么就不存在,接着我们要对v做一次牺牲,让这个点“与世隔绝”,就是把所有以它自己为起点的边全部删掉,如上图所示,如果v为1,就把从1至3的这条边删掉。变成了下图。

lulinrui666

当每次删完一条边之前,我们还要把这条变得终点编号的入度减一(因为这条边已经被删了),这是我们还要判断,如果这是终点的入度为0(删后没有边指向它了),那么我们就把这个点丢入队列中(做好下次输出并牺牲的准备)。

最后,一直循环以上的过程(敲黑板!!!),直到所有边都删完时,整个拓扑排序的过程已经结束了。思路并不复杂,下面贴代码qwq

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define MAXN 1010
int in[MAXN];
vector<int>G[MAXN];//vector数组,头文件是#include<vector>
void topsort(int n)
{
    queue<int>q;//定义一个队列,头文件是#include<queue>
    for(int i=1; i<=n; i++)
    if(!in[i]) q.push(i);//如果这个点的入度为0,就进入队列
    while(!q.empty())//当队列不为空时就进行,想想为什么
    {
        int v=q.front();//取出队中的首项
        q.pop();//并拿出
        cout<<v<<' ';//这时候可以输出v点了
        for(int i=0; i<G[v].size(); i++)//做出牺牲,删边
        {
            in[G[v][i]]--;//终点的入度减一
            if(!in[G[v][i]])//如果终点入度为0
            {
                q.push(G[v][i]);//进入队列
            }
        }
    }
}
int main()
{
    int n,m;
    cin>>n>>m;//读入
    for(int i=1; i<=m; i++)
    {
        int u,v;
        cin>>u>>v;
        in[v]++;//v的入度+1
        G[u].push_back(v);//建一条边
    }
    topsort(n);//拓扑排序
    return 0;
}

##如何在拓扑排序时判环?

如果有时这个图不是一个DAG,只是一个普通的有向图,这时我们拓扑排序时需要判环,简单画个有环的例子。

lulinrui666

如上图所示,点2,3,4构成了一个环.怎样判断环呢,还是很简单
的,按上面所说的过程运行,1节点牺牲时,发现3的入度并没有为0,而1已经牺牲了,另一个点2也到达不了,这样就永远也到达不
了3了QAQ,因为到达不了3,我们就可以判断这个图就是一个存在环的有向图。那怎样去实现呢?只要在以上程序中的while循环中
加一个tot计数器就OK了,每次从队列中拿出一个数时,tot加1,while循环结束的时候判断tot是否等于节点总数,若不等于,则说明图是有环的。

代码:

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define MAXN 1010
int in[MAXN];
vector<int>G[MAXN];//vector数组,头文件是#include<vector>
int ans[MAXN];//存答案的数组
void topsort(int n)
{
    int tot=0,size=0;
    queue<int>q;//定义一个队列,头文件是#include<queue>
    for(int i=1; i<=n; i++)
    if(!in[i]) q.push(i);//如果入读为0,就进入队列
    while(!q.empty())//当队列不为空时就进行,想想为什么
    {
        int v=q.front();//取出队中的首项
        q.pop();//并拿出
        tot++;
        ans[++size]=v;//这是不能输出了,因为可能存在环
        for(int i=0; i<G[v].size(); i++)//做出牺牲,删边
        {
            in[G[v][i]]--;//终点的入度减一
            if(!in[G[v][i]])//如果终点入度为0
            {
                q.push(G[v][i]);//进入队列
            }
        }
    }
    if(tot==n)
    {
        for(int i=1; i<=size; i++)
        cout<<ans[i]<<' ';
    }
    else
    {
        cout<<"No";//有环
    }
}
int main()
{
    int n,m;
    cin>>n>>m;//读入
    for(int i=1; i<=m; i++)
    {
        int u,v;
        cin>>u>>v;
        in[v]++;//v的入读+1
        G[u].push_back(v);//建一条边,vector
    }
    topsort(n);//拓扑排序
    return 0;
}

##有哪些拓扑排序的题目?

洛谷上的P3242(算是一道拓扑排序的水题,可能是假的紫题)

P1038 神经网络 一道很经典的拓扑排序题目

P2017 先拓扑排序单向道路,在比较每个双向道路起点终点的前后顺序

P4742 先用Tarjan缩点,然后用拓扑排序dp

PS:本人蒟蒻一枚,如果此文章有哪里说错了,欢迎在评论区里告诉作者qwq

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值