LeetCode 210. Course Schedule II

210. Course Schedule II

There are a total of n courses you have to take, labeled from 0 to n - 1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, return the ordering of courses you should take to finish all courses.

There may be multiple correct orders, you just need to return one of them. If it is impossible to finish all courses, return an empty array.

For example:

2, [[1,0]]

There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1]

4, [[1,0],[2,0],[3,1],[3,2]]

There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].

Note:

  1. The input prerequisites is a graph represented by a list of edges,
    not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input
    prerequisites.

题目内容:
题目给出课程的总数numCourses和课程之间的依赖关系,即上某些课程的时候,需要先完成一些课程,例如

2, [[1,0]]  //表示总共有2门课,选课程1之前需要先完成课程0

题目的要求是给出一个合法的选课顺序满足给出的依赖条件,如果不可能有合法的顺序,那么输出空的数组。

解题思路:
这个题目是典型的拓扑排序问题。常用的拓扑排序方法有Kahn和DFS(深度优先搜索)。我尝试了分别用这2种方法来解决这个问题。

  • Kahn:
    因为图中的点是有顺序的,有入度的点说明这个点需要依赖指向它的点。所以我们就可以想到入度为0的点就是没有任何依赖的,我们可以马上选择访问这个点,而访问完这个点之后,那么相应的这个从这个点出去的边也应该去掉,所以对于这个点指向的点,入度会减1,如果这个点的入度减1后变为了0,那么可以访问这个点,以此类推。所以,Kahn就是一个不停的找到入度为0的点,和删掉边的过程,如果最后图的边可以全部被删掉,那么每个顶点的拓扑序也就出来了;否则说明这个图不是DAG,不能进行拓扑排序。在程序实现的过程中,可以用一个集合来存储入度为0的点,每次从集合里面选出一个点来访问,访问完后将入度变为0的点也加入集合当中,直到集合为空。下面来用一个简单的例子来说明找出顶点的拓扑序,对于一个DAG,如下图:
    这里写图片描述
    很明显,一开始只有顶点0是入度为0的点,那么先把顶点0放入一个存放拓扑序的数组,并把从顶点0出去的2条边都删掉。然后发现顶点1和顶点2都变成入度为0的点,此时可以访问顶点1,也可以访问顶点2,这里我选择了顶点1。接下来剩下顶点2和顶点3,因为即使删掉从1到3的边,顶点3的入度还是不为0,所以此时也只能选择顶点2。访问完顶点2之后,发现顶点3的入度变成0了,此时所有顶点也都访问完了,我们得到一个拓扑序0->1->2->3。
    而对于一个有环的图,使用Kahn我们也可以发现这个图是有环的,因为对于一个有环的图,在环中的顶点的入度永远都不会变为0,所以环的边是不会被去掉的。所以,如果入度为0的顶点集合为空的时候,图中还依然存在边,那么说明这图是有环的,不能进行拓扑排序。
  • DFS
    对于另外一种拓扑排序方法,是一种基于DFS的方法,与Kahn恰好相反,这种方法从出度为0出发,递归往前找指向这个出度为0的点的那些点,直到找到一个入度为0的点,就可以递归地把点加入拓扑序列的末端。还是以上面简单的DAG例子来说明:
    这里写图片描述
    至于使用DFS怎么发现图是有环的,我们可以给每个顶点设定一个状态,假如这个状态叫temp,如果某个点的状态为temp,表明此时往回找的过程中这个点已经到达过了。所以每访问一个点的时候,如果发现这个点的状态已经是temp,表明图中有环,如下图。
    这里写图片描述
    但是对于下面图的这种情况,用上述的方式是不能检查到图中是否有环的。

    因为在这个图中,顶点3、4、5是根本不会被访问的,所以也不会出现temp状态冲突的问题。要解决这个问题,我们可以简单地判断最后的拓扑序列中的元素个数是否和图中点的个数一致,不一致的话就说明图中有环。

代码:

//
//  main.cpp
//  210. Course Schedule II
//
//  Created by mingjc on 2017/3/19.
//  Copyright © 2017年 mingjc. All rights reserved.
//

#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;


class Solution {
public:
    vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
        //return Kahn(numCourses, prerequisites);
        return DFS(numCourses, prerequisites);
    }

    vector<int> DFS(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<int> result;
        unordered_set<int> visited;
        bool temp[numCourses];
        memset(temp, 0, numCourses * sizeof(bool));
        bool** adjMatrix = new bool*[numCourses]; //邻接矩阵来表示图
        // 将邻接矩阵全设为false
        for (int i = 0; i < numCourses; i++) {
            adjMatrix[i] = new bool[numCourses];
            memset(adjMatrix[i], 0, numCourses * sizeof(bool));
        }
        // 根据输入初始化邻接矩阵,即如果课程1依赖课程0,则将adjMatrix[1][0]赋值为true
        for (int i = 0; i < prerequisites.size(); i++) {
            pair<int, int> p = prerequisites[i];
            adjMatrix[p.first][p.second] = true;
        }

        for (int i = 0; i < numCourses; i++) {
            if (checkBeReq(adjMatrix, i, numCourses).size() == 0) {
                if (visit(visited, adjMatrix, i, numCourses, result, temp) == -1) {
                    result.clear();
                    break;
                }
            }
        }

        // 图中有环
        if (result.size() != numCourses) {
            result.clear();
        }

        return result;

    }

    int visit(unordered_set<int>& visited, bool** adjMatrix, int courseId, int numCourses, vector<int>& result, bool* temp) {
        if (temp[courseId] == true) {
            return -1;
        }
        if (visited.find(courseId) == visited.end()) {
            visited.insert(courseId);
            cout << "visit " << courseId << endl;
            temp[courseId] = true;
            vector<int> req = checkReq(adjMatrix, courseId, numCourses);
            for (int i = 0; i < req.size(); i++) {
                if (visit(visited, adjMatrix, req[i], numCourses, result, temp) == -1) {
                    return -1;
                }
            }
            temp[courseId] = false;
            result.push_back(courseId);
        }
        return 0;
    }

    vector<int> Kahn(int numCourses, vector<pair<int, int>>& prerequisites) {
        vector<int> result;
        unordered_set<int> noIncomeSet;
        bool** adjMatrix = new bool*[numCourses]; //邻接矩阵来表示图
        // 将邻接矩阵全设为false
        for (int i = 0; i < numCourses; i++) {
            adjMatrix[i] = new bool[numCourses];
            memset(adjMatrix[i], 0, numCourses * sizeof(bool));
        }
        // 根据输入初始化邻接矩阵,即如果课程1依赖课程0,则将adjMatrix[1][0]赋值为true
        for (int i = 0; i < prerequisites.size(); i++) {
            pair<int, int> p = prerequisites[i];
            adjMatrix[p.first][p.second] = true;
        }

        for (int i = 0; i < numCourses; i++) {
            if (checkReq(adjMatrix, i, numCourses).size() == 0) {
                noIncomeSet.insert(i);
            }
        }

        while (!noIncomeSet.empty()) {
            int courseId = *noIncomeSet.begin();
            noIncomeSet.erase(noIncomeSet.begin());
            result.push_back(courseId);
            vector<int> beReq = checkBeReq(adjMatrix, courseId, numCourses);
            for (int j = 0; j < beReq.size(); j++) {
                adjMatrix[beReq[j]][courseId] = false;
                if (checkReq(adjMatrix, beReq[j], numCourses).size() == 0) {
                    noIncomeSet.insert(beReq[j]);
                }
            }
        }
        for (int i = 0; i < numCourses; i++) {
            if (checkReq(adjMatrix, i, numCourses).size() != 0) {
                result.clear();
                break;
            }
        }
        return result;
    }

    vector<int> checkReq(bool** adjMatrix, int courseId, int numCourses) {
        vector<int> req;
        for (int i = 0; i < numCourses; i++) {
            if (adjMatrix[courseId][i] == true) {
                req.push_back(i);
            }
        }
        return req;
    }

    vector<int> checkBeReq(bool** adjMatrix, int courseId, int numCourses) {
        vector<int> req;
        for (int i = 0; i < numCourses; i++) {
            if (adjMatrix[i][courseId] == true) {
                req.push_back(i);
            }
        }
        return req;
    }
};

int main(int argc, const char * argv[]) {
    vector<pair<int, int>> vec;
    vec.push_back(std::make_pair (1, 0));
    vec.push_back(std::make_pair (5, 1));
    vec.push_back(std::make_pair (0, 5));

    Solution sln;
    vector<int> order = sln.findOrder(8, vec);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值