拓扑排序-LeetCode210.课程表II

先看一下百度百科对于拓扑排序的定义:
拓扑排序-LeetCode210.课程表II_第1张图片
在这里插入图片描述

简单来说就是在一个有向无环图中寻找一个序列,这个序列包含所有节点,可以线性的一次遍历完所有的节点,这样的序列我们称之为拓扑序列,寻找拓扑队列的过程就是拓扑排序。
通过这道题目我们了解一下拓扑排序的两种实现方式-(dfs&bfs)
(解本题的基本思路就是将已知条件转换为图的数据结构,意识到所求结果就是图中的拓扑序列,通过拓扑排序进行寻找。)
LeetCode210. 课程表 II
先看一下相对简单的BFS思路:对于一个拓扑序列,起点一定是一个没有任何入边的节点,对于这样的节点,我们保存其数据到结果数组,并且将其所有出边断开。随后递归地寻找没有入边的节点,并断边保存。最终结果数组就是一个拓扑排序。
其实现过程就是模拟了一种拓扑序列的完整流程。当然,对于可能存在于图中的环,我们无法在环中取出节点,因为他们总是有入边且无法断掉。通过这个特点可以判断是否存在拓扑序列。

class Solution
{
private:
    vector<vector<int>> edgs; //将课程关系转换为图中的边
    vector<int> counts;       //保存图中每个节点的入边条数
    vector<int> res;
    queue<int> search; //用于在BFS过程中保存可遍历节点
public:
    void bfs(int i)
    {
        for (int j = 0; j < edgs[i].size(); j++)
        {
            counts[edgs[i][j]]--;
            if (counts[edgs[i][j]] == 0)
                search.push(edgs[i][j]);
        }
        res.push_back(i);
    }
    vector<int> findOrder(int numCourses, vector<vector<int>> &prerequisites)
    {
        //数据预处理,体重数据转换为一个有向图
        counts.resize(numCourses, 0);
        edgs.resize(numCourses);
        for (auto &temp : prerequisites)
        {
            counts[temp[0]]++;
            edgs[temp[1]].push_back(temp[0]);
        }
        //找到可以进入的节点,即拓扑排序的始节点
        for (int i = 0; i < numCourses; i++)
        {
            if (counts[i] == 0)
                search.push(i);
        }
        //不断地从队列中取出可处理节点,直到队列为空
        while (!search.empty())
        {
            bfs(search.front());
            search.pop();
        }
        //判断是否得到完整的拓扑序列
        if (res.size() != numCourses)
        {
            return {};
        }
        return res;
    }
};

再来看DFS的思路:我们从DFS的思路出发,对于途中任何一个节点,将这个节点所有的路径都走到底,全部走一遍,当遇到一个没有出边的节点时,我们保存该节点对应的值,随后回溯这条路径即从低向上保存路径上的节点(可以理解为自底向上的断开每个结点的出边)。
对于整个图来说,我们遍历所有节点,为避免重复,对每一次DFS过程中访问过的节点我们设置标记变量标记为访问过。整个图DFS完成得到的数组就是一种拓扑序列的逆序形式。
现在来考虑不存在拓扑序列即图中有环的情况:我们利用标志变量标记正在访问的节点为搜索中状态,当从这个节点向下的DFS过程中存在遇到另一个遍历中的节点时,我们就可以判定图中存在一个环。(注意题目的意思是如果存在[1,0][1,2]时,需同时满足0,2均已完成)
考虑一种情况:当一个无出度节点同时被多个节点指向时,我们在一条路径上搜索到他就可以将其保存下来,因为没有出度所以它必定不是环中的节点,那么我们默认其可以到达,随后只要判断其他节点是否成环即可。

class Solution
{
private:
    vector<vector<int>> edges;
    vector<int> visited; //标志节点的当前状态:0未完成 1正在搜索 2已完成
    vector<int> result;  //用于输出结果,实际上是模拟了一个栈
    bool legal = true;   //标记是否有环的存在,当遇到环时后续遍历就不用进行下去了,直接return或者break

public:
    void dfs(int point)
    {
        visited[point] = 1;
        for (auto &i : edges[point])
        {
            if (visited[i] == 1) //搜索中节点遇到另一个搜索中节点,即成环
            {
                legal = false;
                return;
            }
            if (!visited[i]) //对于未搜索过的节点向下搜索
            {
                dfs(i);
                if (!legal)
                    return;
            }
        }
        visited[point] = 2; //每个搜索过的节点即使改变状态
        result.push_back(point);
        return;
    }
    vector<int> findOrder(int numCourses, vector<vector<int>> &prerequisites)
    {
        //数据预处理,构成一个图的数据结构
        edges.resize(numCourses);
        visited.resize(numCourses);
        for (auto &i : prerequisites)
        {
            edges[i[1]].push_back(i[0]);
        }
        for (int i = 0; i < numCourses && legal; i++)
        {
            //没有环时遍历所有未被搜索过的节点
            if (!visited[i])
                dfs(i);
        }
        if (!legal)
            return {};
        reverse(result.begin(), result.end());
        return result;
    }
};

你可能感兴趣的:(leetcode,算法,dfs)