有向图的拓扑序列

278 阅读2分钟

给定一个 nn 个点 mm 条边的有向图,点的编号是 11 到 nn,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 1−1

若一个由图中所有点构成的序列 AA 满足:对于图中的每条边 (x,y)(x,y)xx 在 AA 中都出现在 yy 之前,则称 AA 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 nn 和 mm

接下来 mm 行,每行包含两个整数 xx 和 yy,表示存在一条从点 xx 到点 yy 的有向边 (x,y)(x,y)

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 1−1

数据范围

1n,m1051≤n,m≤10^5

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3

题目分析

这是一道拓扑排序的应用。

首先我们统计每个点的入度,入度即以当前节点为边的箭头端的边的数量。统计完之后,我们创建一个队列,遍历每一个节点并将所有入度为 00 的点压入队列,每次弹出队头节点,并将与队头节点相连的边删去,对应的操作便是将相连边的另一个箭头端的节点的入度减 11,若当前节点的入度便为 00,则将当前节点压入队列。

我们来分析以上拓扑排序的作用,对于每一个节点来说,它所连接边的箭头端节点一定比它更后入队,这也便意味着在最终的答案数组中其顺序一定后于当前节点,我们将所有节点如此入队并提取,最终比较答案数组的元素数量是否为所有节点数便可以判断是否可以产生拓扑序列,若不能则意味着有向图存在环。

经过以上分析我们同时发现重边和自环对过程不造成影响。

由于每个点仅会入队出队一次,每条边只会遍历一次,时间复杂度为 O(n+m)O(n + m)

Accept代码

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
vector<int> g[N], res;
int d[N];
int n, m;

bool topsort()
{
    queue<int> q;
    for (int i = 1; i <= n; i ++)
        if (!d[i]) q.push(i);
    while (!q.empty())
    {
        int t = q.front(); q.pop();
        res.push_back(t);
        for (auto x : g[t])
        {
            d[x] --;
            if (!d[x]) q.push(x);
        }
    }
    return res.size() == n;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    while (m --)
    {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        d[b] ++;
    }
    if (topsort()) for (auto x : res) cout << x << ' ';
    else cout << -1;
    return 0;
}