课程表

LeetCode算法网站算法题

https://leetcode-cn.com/problems/course-schedule/

这道题的思路就是把课程表之间的关系转换为有向图,并检测有向图中是否有环

一.深度优先搜索

算法:

对于图中的每一个节点都有三个状态

(1)未搜索:我们还没有搜索到这个节点;

(2)搜索中:我们搜索过这个节点,但还没有回溯到该节点,即该节点还没有入栈,还有相邻的节点没有搜索完成);

(3)已完成:我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。

通过上述的三种状态,我们就可以给出使用深度优先搜索得到拓扑排序的算法流程,在每一轮的搜索搜索开始时,我们任取一个「未搜索」的节点开始进行深度优先搜索。

我们将当前搜索的节点 u 标记为「搜索中」,遍历该节点的每一个相邻节点 v:

(1)如果 v 为「未搜索」,那么我们开始搜索 v,待搜索完成回溯到 u;

(2)如果 v 为「搜索中」,那么我们就找到了图中的一个环,因此是不存在拓扑排序的;

(3)如果 v 为「已完成」,那么说明 v 已经在栈中了,而 u 还不在栈中,因此 u 无论何时入栈都不会影响到 (u,v) 之前的拓扑关系,以及不用进行任何操作。

当 u 的所有相邻节点都为「已完成」时,我们将 u 放入栈中,并将其标记为「已完成」。

在整个深度优先搜索的过程结束后,如果我们没有找到图中的环,那么栈中存储这所有的 n 个节点,从栈顶到栈底的顺序即为一种拓扑排序。

最近需要学习Java的语法,所以使用Java来写题

class Solution {
    private List<List<Integer>> pic;//图的邻接表数组
    private int[] mark;//节点访问标记
    private boolean flag=true;//判断有向图是否有环
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //创建邻接表的数组
        pic=new ArrayList<List<Integer>>();
        for (int i=0;i<numCourses;i++){
            pic.add(new ArrayList<Integer>());
        }
        //设置遍历的标记数组
        mark=new int[numCourses];
        //绘制图
        for(int[] info:prerequisites){
            pic.get(info[1]).add(info[0]);
        }
        //遍历图做判断
        for (int i=0;i<numCourses&&flag;i++){
            if (mark[i]==0){//没有遍历过的点就继续遍历
                dfs(i);
            }
        }
        return flag;
    }
    private void dfs(int u){
        mark[u]=1;
        for (int v:pic.get(u)){
            if (mark[v]==0){//没有遍历过的点就继续遍历
                dfs(v);
                if (!flag){//如果已经有环了直接终止遍历
                    return;
                }
            }else if (mark[v]==1){//如果当前节点已经遍历过,同时遍历它的邻接节点的操作并没有完成那么说明有环
                System.out.println("错误"+u+v);
                flag=false;
                return;
            }
            
        }
        //上面操作都完成了说明该节点遍历完成
        mark[u]=2;
    }
}

二.广度优先搜索

思路:
方法一的深度优先搜索是一种「逆向思维」:最先被放入栈中的节点是在拓扑排序中最后面的节点。我们也可以使用正向思维,顺序地生成拓扑排序,这种方法也更加直观。
我们考虑拓扑排序中最前面的节点,该节点一定不会有任何入边,也就是它没有任何的先修课程要求。当我们将一个节点加入答案中后,我们就可以移除它的所有出边,代表着它的相邻节点少了一门先修课程的要求。如果某个相邻节点变成了「没有任何入边的节点」,那么就代表着这门课可以开始学习了。按照这样的流程,我们不断地将没有入边的节点加入答案,直到答案中包含所有的节点(得到了一种拓扑排序)或者不存在没有入边的节点(图中包含环)。
上面的想法类似于广度优先搜索,因此我们可以将广度优先搜索的流程与拓扑排序的求解联系起来。

算法:
我们使用一个队列来进行广度优先搜索。初始时,所有入度为 0 的节点都被放入队列中,它们就是可以作为拓扑排序最前面的节点,并且它们之间的相对顺序是无关紧要的。
在广度优先搜索的每一步中,我们取出队首的节点 u:

(1)我们将 u 放入答案中;

(2)我们移除 u 的所有出边,也就是将 u 的所有相邻节点的入度减少 1。如果某个相邻节点 v 的入度变为 0,那么我们就将 v 放入队列中。

在广度优先搜索的过程结束后。如果答案中包含了这 n 个节点,那么我们就找到了一种拓扑排序,否则说明图中存在环,也就不存在拓扑排序了。

下面是我直接扒下来的C++代码,有时间再想

class Solution {
private:
    vector<vector<int>> edges;
    vector<int> indeg;

public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        edges.resize(numCourses);
        indeg.resize(numCourses);
        for (const auto& info: prerequisites) {
            edges[info[1]].push_back(info[0]);
            ++indeg[info[0]];
        }

        queue<int> q;
        for (int i = 0; i < numCourses; ++i) {
            if (indeg[i] == 0) {
                q.push(i);
            }
        }

        int visited = 0;
        while (!q.empty()) {
            ++visited;
            int u = q.front();
            q.pop();
            for (int v: edges[u]) {
                --indeg[v];
                if (indeg[v] == 0) {
                    q.push(v);
                }
            }
        }

        return visited == numCourses;
    }
};

 

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页