leetcode之路(207.课程表)
本文详细介绍有向图的拓扑排序
207.课程表 题目:
现在你总共有 n 门课需要选,记为 0 到 n-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。这是不可能的。
说明:输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法。 你可以假定输入的先决条件中没有重复的边
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/course-schedule
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
“实现一个功能如果需要10分钟,思考的时间是7分钟”
思考过程:
题意就是给定一个整数n,和一系列的关系数组【a,b】;n表示一共有n门课程;在后面的每一个数组中:a是b的先行课(必须先学课程a,才能学习课程b),就像是必须先学习了编程语言,才能学数据结构;可以将每一个关系数组【a,b】看成是一条有向弧,a->b;题目的要求是** 让判断是否可以完成所有课程,那么我们思考什么时候不能完成所有课程呢?**题目中有当关系数组为[[0,1][1,0]]的时候由于0和1互为对方的先行课,导致矛盾,无法完成这两门课程。进一步思考,不难发现只要我们用关系数组构成的有向图是有环的就无法完成所有课程,由此,此问题便转换成了判断有向图是否有环的问题。对于此类问题,我们可以用拓扑排序来检测,从而轻松解决。下面介绍拓扑排序的方法:
拓扑排序
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological
Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
–百度词条
拓扑排序借助一个存有各个点入度的数组和一个n个数组,第i(0<=i<n)个数组存有以第i个点为弧尾(没有箭头的那端)的点的序号(也可以用链表结构);
结合这个题目,一开始就可以上的课一定是没有先行课的(有向图上一开始入读就为0的点),后续的可以上的课一定是先行课都已经上过了的课(有向图上去掉已经上过的课所对应顶点后,入度为0的点);
举个例子:输入:n=5,[[1,2],[1,3],[2,3],[3,4]],[4,5],[5,3]]
我们可以得到图1,并求得每个点的入度存入数组in,此时入度为0的点只有点1,于是我们去掉点1,并把以1为弧尾的弧所指的点的度数减1,得到图2,然后我们发现点2的度数变为了0,于是又可以按照去掉点1的方法去掉点2,得到图三。
图1:
图2:
图3:
然后发现没有新出现入度为0的点了,拓扑排序就结束了,我们可以记录下排序好的点的个数,如果排好的点的个数与点的总个数相同则该有向图不含有环,反之,该图有环。
在编程过程中我们一般用队列或者栈暂时的存储入度为0的点,每pop()出一个点,并使cnt++(用来计算排好点的个数),就更新in数组,同时把更新后入度新变为0的点存入栈或队列中等待下一步pop();
以上就是这个拓扑排序的思想。
附上代码:
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites){
//计算各点入度,并存入数组in中
vector<int> in(numCourses,0);
for(int i=0;i<prerequisites.size();i++){
in[prerequisites[i][1]]++;
}
//把开始时入度即为0的点入栈
stack<int> st;
for(int i=0;i<in.size();i++){
if(in[i]==0) st.push(i);
}
//next数组用以储存各点所指的点
vector<vector<int>> next(numCourses);
for(int i=0;i<prerequisites.size();i++){
next[prerequisites[i][0]].push_back(prerequisites[i][1]);
}
int cnt=0;
while(!st.empty()){
int i=st.top();
st.pop();
cnt++;
//把以点i为弧尾的弧所指的点的入度减1,若为0,则入栈;
for(int j=0;j<next[i].size();j++){
if(--in[next[i][j]]==0) st.push(next[i][j]);
}
}
//最后判断排好序的点的个数是否与总个数相等,若相等,则该图无环,反之则有。
if(cnt==numCourses) return true;
return false;
}
};
提交结果
执行结果:通过
执行用时 :16 ms, 在所有 cpp 提交中击败了99.60%的用户
内存消耗 :12.8 MB, 在所有 cpp
提交中击败了19.56%的用户