算法(七)Course Schedule 拓扑排序

题目描述

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, is it possible for you to finish all courses?

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 it is possible.

2, [[1,0],[0,1]]
There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible.

解题思路

  1. 根据题目意思我们可知,左边的数字要在右边的数字完成后才可完成。我们采用一个容器set放没有出现在左边的数字,即不需要等待其他数字先完成的数字。接着遍历整个vector,遍历的次数为vector的大小,每次遍历都找寻一个pair的右边数字出现在容器set中的,然后从vector中删除这个pair,因为这个依赖是可以完成的。每次删除后,要查看刚刚删除的pair左边数字还有没有出现在容器中某一pair元素的左边,如果没有则该数字不依赖任何其他数字,所以可以加入set容器中。重复执行下去,如果最后set的大小为n,vector容器大小为0,则证明可以完成。具体实现代码如下:

    class Solution {
    public:
        bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
            set <int> hasFinished;
            //两个for循环初始化容器set
            for (int i = 0; i < numCourses; ++i)
                hasFinished.insert(i);
            vector< pair<int, int> >::iterator iter = prerequisites.begin();
            for (; iter != prerequisites.end(); ++iter) {
                hasFinished.erase(iter->first);
            }
            int number = prerequisites.size();
            for (int i = 0; i < number; ++i) {
                for (iter = prerequisites.begin(); iter != prerequisites.end(); ++iter) {
                    if (hasFinished.find(iter->second) != hasFinished.end()) {
                        int temp = iter->first;
                        prerequisites.erase(iter);
                        //该数字是否不再依赖其他数字,如果是加入容器set中。
                        int k = 0;
                        for (k = 0; k < prerequisites.size(); ++k) {
                            if (prerequisites[k].first == temp) {
                                break;
                            }
                        }
                        if (k == prerequisites.size()) {
                            hasFinished.insert(temp);
                        }
                        break;
                    }
                }
                //下面这两行的目的是当遍历vector时没有可剔除的则可直接退出,不必再循环下去。但是加上之后是错的,因为当删除vector元素时iter已经失效。
                //if (iter == prerequisites.end())
                //  break;
            }
            if (hasFinished.size() == numCourses)
                return true;
            return false;
        }
    };
  2. 复杂度更好的做法应该是使用拓扑排序。现在我们采用DFS,代码如下:

    class Solution {
    public:
        bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
            vector<bool> onpath(numCourses, false), visited(numCourses, false);
            for (int i = 0; i < numCourses; i++)
                if (!visited[i] && dfs_cycle(graph, i, onpath, visited))
                    return false;
            return true;
        }
    private:
        //构建一个图
        vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph(numCourses);
            for (auto pre : prerequisites)
                graph[pre.second].insert(pre.first);
            return graph;
        } 
        //从某个节点开始进行深度优先遍历
        bool dfs_cycle(vector<unordered_set<int>>& graph, int node, vector<bool>& onpath, vector<bool>& visited) {
            if (visited[node]) return false;
            onpath[node] = visited[node] = true; 
            for (int neigh : graph[node])
                if (onpath[neigh] || dfs_cycle(graph, neigh, onpath, visited))
                    return true;
            return onpath[node] = false;
        }
    };

    解析:如果拓扑排序是成功的,则从一个点开始的深度优先遍历不会遍历到已经在遍历路径上的节点,否则就构成了一个环。所以用onpath记录每个节点是否在路径上。如果从第一个节点出发深度遍历,路径上不出现重复点则没有环。然后再查询有无没遍历过的节点,已遍历过的节点无需再遍历,因为该节点路径上的点都已被遍历。如果有还没遍历过的点则进行遍历,同样是不能有环的存在。但是此次遍历路径上可能会有已遍历过的点,这时对于该已遍历的点无需再次遍历。(因为他指向的节点时可以先完成,然后它自然也可以跟着完成)同时,每次深度优先遍历完要把onpath值重新设置为false。

  3. 现在采用BFS来完成拓扑排序。记录每个节点的入度数,然后通过循环将入度为0的数去掉并将其指向的节点入度减一。代码如下:

    class Solution {
    public:
        bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
            vector<int> degrees = compute_indegree(graph);
            for (int i = 0; i < numCourses; i++) {
                int j = 0;
                for (; j < numCourses; j++)
                    if (!degrees[j]) break;
                if (j == numCourses) return false;
                degrees[j] = -1;
                for (int neigh : graph[j])
                    degrees[neigh]--;
            }
            return true;
        }
    private:
        vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
            vector<unordered_set<int>> graph(numCourses);
            for (auto pre : prerequisites)
                graph[pre.second].insert(pre.first);
            return graph;
        }
        vector<int> compute_indegree(vector<unordered_set<int>>& graph) {
            vector<int> degrees(graph.size(), 0);
            for (auto neighbors : graph)
                for (int neigh : neighbors)
                    degrees[neigh]++;
            return degrees;
        }
    };
  4. 补充:在已知无环的前提下,通过DFS记录pre值(开始访问该节点)和post值(子节点全都访问完),按post值递减排列,即可得到合法序列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值