C++ 树立课程表的正确先后关系

课程安排

题目

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:
输入: 2, [[1,0]]
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

提示:
1.输入的先决条件是由 边缘列表 表示的图形,而不是 邻接矩阵 。详情请参见图的表示法。
2.你可以假定输入的先决条件中没有重复的边。
3.1 <= numCourses <= 105

思路

对于n个课程,它们之间有m个依赖关系,可以看成顶点个数为n,边个数为m的有向图。
如:图1:n = 3,m = [[0,1],[0,2][1,2]];可以完成。

图2:n = 3, m = [[0,1],[1,2],[2,0]];不可以完成。

所以,对于有向无环图,则可以完成全部课程,否则不能完成。问题则转换成,构建图,并**判断图是否有环**。

利用节点访问状态,-1没有访问过,0代表正在访问,1表示已完成访问。

有关图的基础知识点击:图的基础知识

方法1:使用深度优先搜索,如果正在探索的某一顶点(还未推出递归深度搜索),又回到了已经搜索过的顶点,即证明图有环。(如下图所示)

代码实现如下:

struct GraphNode{
    int label;
    vector<GraphNode*> neighbors;
    GraphNode(int x) : label(x) {};
};

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<GraphNode*> graph;
        vector<int> visit;
        for(int i = 0; i < numCourses; i++){
            graph.push_back(new GraphNode(i)); //创建图的节点,并赋访问状态为空
            visit.push_back(-1); //创建图,连接图的顶点
        }
        for(int i = 0; i < prerequisites.size(); i++){
            GraphNode* begin = graph[prerequisites[i][1]];
            GraphNode* end = graph[prerequisites[i][0]];
            begin->neighbors.push_back(end);
        }
        for(int i = 0; i < graph.size(); i++){
            if(visit[i] == -1 && !DFS_graph(graph[i],visit)){
                return false;
            }
        }
        for(int i = 0; i < numCourses; i++){
            delete graph[i];
        }
        return true;
    }

private:
    bool DFS_graph(GraphNode* node, vector<int> &visit){
        visit[node->label] = 0;
        for(int i = 0; i < node->neighbors.size(); i++){
            if(visit[node->neighbors[i]->label] == -1 ){ //当该顶点未被访问
                if(DFS_graph(node->neighbors[i], visit) == 0){ //再次进行深度访问
                    return false;
                }
            }
            else if (visit[node->neighbors[i]->label] == 0){
                return false;
            }
        }
        visit[node->label] = 1;
        return true;
    }
};

方法2:利用宽度优先搜索,只将入度为0的点添加至队列。当完成一个顶点的搜索(从队列取出),它指向的所有顶点入读都减1,若此时某顶点入度为0则添加至队列,若完成宽度搜索后,所有的顶点入度都为0,则图无环,否则有环
过程如下图所示:
无环时

有环时

实现代码如下:

struct GraphNode{
    int label;
    vector<GraphNode*> neighbors;
    GraphNode(int x) : label(x) {};
};

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<GraphNode*> graph;
        vector<int> degree;
        for(int i = 0; i < numCourses; i++){
            degree.push_back(0);
            graph.push_back(new GraphNode(i));
        }
        for(int i = 0; i < prerequisites.size(); i++){
            GraphNode* begin = graph[prerequisites[i][1]];
            GraphNode* end = graph[prerequisites[i][0]];
            begin->neighbors.push_back(end);
            degree[prerequisites[i][0]]++;//入度+1
        }
        queue<GraphNode*> Q;
        for(int i = 0; i < numCourses; i++){
            if(degree[i] == 0){
                Q.push(graph[i]);
            }
        }
        while(!Q.empty()){
            GraphNode* node = Q.front();
            Q.pop();
            for(int i = 0; i < node->neighbors.size(); i++){
                degree[node->neighbors[i]->label]--;
                if(degree[node->neighbors[i]->label] == 0){
                    Q.push(node->neighbors[i]);
                }
            }
        }
        for(int i = 0; i < graph.size(); i++){
            delete graph[i];
        }
        for(int i = 0; i < degree.size(); i++){
            if(degree[i]){
                return false;
            }
        }
        return true;
    }
};

致谢

本章知识点和思路由小象学院相关视频提供,由本人学习并梳理得出,希望自己加深记忆的同时,也能给大家提供更多有关于一些算法的知识点。
你的点赞评论收藏就是对我最大的支持与鼓励,谢谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值