数据结构与算法 - 拓扑排序

基础

有向图的表示

假设有向图有 n 个节点,按 0n - 1 编号,其中 n 是 graph 的节点数。图以下述形式给出:graph[i] 是编号 j 节点的一个列表,满足 (i, j) 是图的一条有向边。

image

输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]

输出:[2,4,5,6]

解释:示意图如上。

graph[i][……]=x 代表 i 到 x 是图的一条有向边。

拓扑排序

在图论中,一个有向无环图必然存在至少一个拓扑序与之对应,反之亦然。

简单来说,就是将图中的所有节点展开成一维序列,对于序列中任意的节点 \((u, v)\) ,如果在序列中 \(u\)\(v\) 的前面,则说明在图中存在从 \(u\) 出发达到 \(v\) 的通路,即 \(u\) 排在 \(v\) 的前面。反之亦然。

同时,我们需要知晓「入度」和「出度」的概念:

  • 入度:有多少条边直接指向该节点;
  • 出度:由该节点指出边的有多少条。

因此,对于有向图的拓扑排序,我们可以使用如下思路输出拓扑序(BFS 方式):

  1. 起始时,将所有入度为 0 的节点进行入队(入度为 0 ,说明没有边指向这些节点,将它们放到拓扑排序的首部,不会违反拓扑序定义);
  2. 从队列中进行节点出队操作,出队序列就是对应我们输出的拓扑序。对于当前弹出的节点 \(x\) ,遍历 \(x\) 的所有出度,即遍历所有由 \(x\) 直接指向的节点 \(y\),对 \(y\) 做入度减一操作(因为 \(x\) 节点已经从队列中弹出,被添加到拓扑序中,等价于从 \(x\) 节点从有向图中被移除,相应的由 \(x\) 发出的边也应当被删除,带来的影响是与 \(x\) 相连的节点 \(y\) 的入度减一);
  3. \(y\) 进行入度减一之后,检查 \(y\) 的入度是否为 0,如果为 0 则将 \(y\) 入队(当 \(y\) 的入度为 0,说明有向图中在 \(y\) 前面的所有的节点均被添加到拓扑序中,此时 \(y\) 可以作为拓扑序的某个片段的首部被添加,而不是违反拓扑序的定义);
  4. 循环流程 2、3 直到队列为空。

代码实现

class Solution
{
public:
    void topologicalSorting(vector<vector<int>> &graph) {
        int gSize = graph.size();
        // 求顶点入度值
        vector<int> inEdge(gSize, 0);
        for(auto &edge : graph) {
            for(int i:edge) {
                ++inEdge[i];
            }
        }
        deque<int> d;
        for(int i=0; i < gSize; i++) {
            if(inEdge[i] == 0) d.push_back(i);
        }

        while (!d.empty())
        {
            int poll = d.front();
            d.pop_front();
            // 队列顺序就是拓扑排序顺序
            for(int i = 0; i < graph[poll].size(); i++) {
                --inEdge[graph[poll][i]];
                if(inEdge[graph[poll][i]] == 0) {
                    d.push_back(graph[poll][i]);
                }
            }
        }
    }
};
posted @ 2022-01-04 09:10  Logan_Xu  阅读(81)  评论(0编辑  收藏  举报