图——拓扑排序

拓扑排序定义

给定一个包含 n 个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:

对于图 G 中的任意一条有向边 (u, v),u 在排列中都出现在 v 的前面。

那么称该排列是图 G 的「拓扑排序」。

 

题目与解析

lc 210. 课程表 II

 

我们可以将本题建模成一个求拓扑排序的问题了:

我们将每一门课看成一个节点;

如果想要学习课程 A之前必须完成课程 B,那么我们从 B 到 A 连接一条有向边。这样以来,在拓扑排序中,B 一定出现在A的前面。

 

思路:

用一个栈来存储所有已经搜索完成的节点。我们对图进行一遍深度优先搜索。当每个节点进行回溯的时候,我们把该节点放入栈中。最终从栈顶到栈底的序列就是一种拓扑排序。

class Solution {
private:
    // 存储有向图
    vector<vector<int>> edges;
    // 标记每个节点的状态:0=未搜索,1=搜索中,2=已完成
    vector<int> visited;
    // 用数组来模拟栈,下标 0 为栈底,n-1 为栈顶
    vector<int> result;
    // 判断有向图中是否有环
    bool valid = true;

public:
    void dfs(int u) {
         // 将节点标记为「搜索中」
        visited[u] = 1;
        // 搜索其相邻节点
        // 只要发现有环,立刻停止搜索
        for (auto v: edges[u]) {
            // 如果「未搜索」那么搜索相邻节点
            if (visited[v] == 0) {
                dfs(v);
            }
            // 如果「搜索中」说明找到了环
            if (visited[v] == 1) {
                valid = false;
                return;
            }
        }
        // 将节点标记为「已完成」
        visited[u] = 2;
        // 将节点入栈
        result.push_back(u);
    }

    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        edges.resize(numCourses);  // 从每个节点出发的有向边
        visited.resize(numCourses);
        // 构建有向图
        for (const auto& info: prerequisites) {  //注意这里const auto& info的含义, 用和不用都能通过
            edges[info[1]].push_back(info[0]);  //从info[1]的节点指向info[0]的节点(可以有中间节点)
        }
        // 每次挑选一个「未搜索」的节点,开始进行深度优先搜索
        for (int i = 0; i < numCourses; i++) {
            if (!visited[i]) {
                dfs(i);
            }
        }

        if (!valid) {  // 有环
            return {};
        }

        // 如果没有环,那么就有拓扑排序
        // 注意下标 0 为栈底,因此需要将数组反序输出
        reverse(result.begin(), result.end());
        return result;
    }
};

  

 

 

参考链接:

https://leetcode.cn/problems/course-schedule-ii/solution/ke-cheng-biao-ii-by-leetcode-solution/

*题解中「相邻节点」指的是从 出发通过一条有向边可以到达的所有节点。

posted @ 2022-12-18 22:36  blogzzt  阅读(36)  评论(0编辑  收藏  举报