拓扑排序(Topological Sorting)

简介(Introduction)

对一个 有向无环图 (Directed Acyclic Graph 简称 \(DAG\) ) \(G\) 进行拓扑排序,是将 \(G\) 中所有顶点排成一个线性序列,使得图中任意一对顶点 \(u\)\(v\)若边 \(<u,v>\in E(G)\),则 \(u\) 在线性序列中出现在 \(v\) 之前。
通常,这样的线性序列称为满足 拓扑次序 (Topological Order) 的序列,简称拓扑序列。



描述(Description)

  • 如果一个序列中 存在环,那么一定 不存在拓扑序列

  • 一个 有向无环图 一定存在一个拓扑序列 —— 有向无环图也称为 拓扑图

  • 入度和出度

    • 入度: 通常指有向图中某点作为图中边的 终点的次数之和,即有多少条边指向这个点
    • 出度: 通常指顶点的 出边条数

    Tips: 如果一个图 不存在 入度为 \(0\) 的点,那么它一定不是 拓扑图。即 —— 存在自环
    因此可以通过把 当前位置入度为 \(0\) 的点 放入队列中,使用 \(BFS\) 搜索下一层 当前位置入度为 \(0\) 的点

  • 在有向无环图中,把所有点排成一列,使得所有有向边在序列中都是从 左指向右 的操作称为 拓扑排序

  • 时间复杂度\(O(n + m)\)



示例(Example)

  • 有向无环图
    image

  • 拓扑排序
    image



代码(Code)

// C++ Version

bool top_sort() {
    int hh = 0, tt = -1;
    //遍历每一个节点,如果入度为0则加入队列
    for (int i = 1; i <= n; i ++ ) {
        if (!d[i]) {
            q[ ++ tt ] = i;
        }
    }
    while (hh <= tt) {  // 队列不空
        int t = q[hh ++ ];  // 取出头节点
        //遍历头节点的每一个出边
        for (int i = First[t]; ~i; i = Next[i]) {
            int j = Edge[i];
            //遍历到的节点入度 减1, 如果入度为0,则加入队列
            if ( --d[j] == 0) q[ ++ tt ] = j;
        }
    }
    //判读是否全部遍历完毕,如果没有,说明存在有环
    return tt == n - 1;
}



应用(Application)



有向图的拓扑序列


给定一个 \(n\) 个点 \(m\) 条边的有向图,点的编号是 \(1\)\(n\),图中可能存在重边和自环。

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

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

输入格式

第一行包含两个整数 \(n\)\(m\)

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

输出格式

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

否则输出 \(-1\)

数据范围

\(1 \le n,m \le 10^5\)

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3
  • 题解:

    // C++ Version
    
    #include <iostream>
    
    using namespace std;
    
    const int N = 1e5 + 10;
    
    int n, m;    //存储节点n 和 边m
    int First[N], Next[N], Edge[N], idx;
    int q[N];   //队列
    int d[N];   //入度
    
    //构建邻接表
    void add(int a, int b) {
        Edge[idx] = b, Next[idx] = First[a], First[a] = idx++;
    }
    
    //拓扑排序
    bool top_sort() {
        int hh = 0, tt = -1;
        //遍历每一个节点,如果入度为0则加入队列
        for (int i = 1; i <= n; i ++ )
            if (!d[i]) q[++tt] = i;
    
        //队列不空
        while (hh <= tt) {
            //取出头节点
            int t = q[hh ++ ];
            //遍历头节点的每一个出边
            for (int i = First[t]; i != -1; i = Next[i]) {
                int j = Edge[i];
                //遍历到的节点入度 减1, 如果入度为0,则加入队列
                if ( -- d[j] == 0) q[ ++ tt] = j;
            }
        }
        //判读是否全部遍历完毕,如果没有,说明存在有环
        return tt == n - 1;
    }
    
    int main() {
        cin >> n >> m;
        memset(First, -1, sizeof First);
        while (m -- ) {
            int a, b;
            cin >> a >> b;
            add(a, b);  // 构建邻接表
            d[b] ++;  // 入度 加1
        }
    
        // 可以完成拓扑排序
        if (top_sort()) {
            for (int i = 0; i < n; ++ i ) cout << q[i] << ' ';
            puts("");
        }
        else puts("-1");
        return 0;
    }
    

posted @ 2023-05-06 14:43  FFex  阅读(28)  评论(0编辑  收藏  举报