「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
1、题目
你这个学期必须选修 numCourses门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 1050 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
2、思路
拓扑排序:
对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若<u,v> ∈E(G),则u在线性序列中出现在v之前。
一个合法的选课序列就是一个拓扑序,拓扑序是指一个满足有向图上,不存在一条边出节点在入节点前的线性序列,如果有向图中有环,就不存在拓扑序。可以通过拓扑排序算法来得到拓扑序,以及判断是否存在环。
拓扑排序步骤:
- 1、建图并记录所有节点的入度。
- 2、将所有入度为
0的节点加入队列。 - 3、取出队首的元素
now,将其加入拓扑序列。 - 4、访问所有
now的邻接点nxt,将nxt的入度减1,当减到0后,将nxt加入队列。 - 5、重复步骤
3、4,直到队列为空。 - 6、如果拓扑序列个数等于节点数,代表该有向图无环,且存在拓扑序。
时间复杂度分析: 假设 n 为点数,m 为边数,拓扑排序仅遍历所有的点和边一次,故总时间复杂度为 O(n+m)。
3、c++代码
class Solution {
public:
/**
1、建图并记录所有节点的入度。
2、将所有入度0的节点加入队列。
3、取出队首的元素now,将其加入拓扑序列。
4、访问所有now的邻接点nxt,将nxt的入度减1,当减到0后,将nxt加入队列。
5、重复步骤3、4,直到队列为空。
6、如果拓扑序列个数等于节点数,代表该有向图无环,且存在拓扑序。
**/
bool canFinish(int n, vector<vector<int>>& edges) {
vector<vector<int>> g(n);
vector<int> d(n); // 存贮每个节点的入度
for(auto edge : edges){
g[edge[1]].push_back(edge[0]); //建图
d[edge[0]]++; //入度加1
}
queue<int> q;
for(int i = 0; i < n; i++){
if(d[i] == 0) q.push(i); //将所有入度为0的节点加入队列。
}
int cnt = 0; //统计拓扑节点的个数
while(q.size()){
int t = q.front();
q.pop();
cnt++;
for(int i : g[t]){ //访问t的邻接节点
d[i]--;
if(d[i] == 0) q.push(i);
}
}
return cnt == n;
}
};