描述
你这个学期必须选修 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 <= numCourses <= 10^5
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/course-schedule/
求解
class Solution {
private:
/*!
*
* @param graph 图,邻接表
* @param inDegree 顶点入度
* @param items 待排序顶点
* @return 拓扑排序结果
*/
vector<int>
topSort_useStack(const vector<vector<int>> &graph, vector<int> &inDegree, const vector<int> &items) {
// 找出入度为0的点开始遍历
std::stack<int> s;
for (auto i : items) {
if (inDegree[i] == 0) {
s.push(i); // 顶点入度为0,入栈
}
}
// 拓扑排序
vector<int> res;
while (!s.empty()) {
int v = s.top();
res.push_back(v); // 保存拓扑排序结果
s.pop();
for (auto w : graph[v]) {
if (--inDegree[w] == 0) {
s.push(w);
}
}
}
return res;
}
// 利用拓扑排序判断是否存在环,不关心拓扑排序结果
bool topSortJudgeCircle(const vector<vector<int>> &graph, vector<int> &inDegree, const vector<int> &items) {
// 找出入度为0的点开始遍历
std::stack<int> s;
for (auto i : items) {
if (inDegree[i] == 0) {
s.push(i); // 顶点入度为0,入栈
}
}
// 拓扑排序
int count = 0;
while (!s.empty()) {
++count; // 拓扑排序节点数累加
int v = s.top();
s.pop();
for (auto w : graph[v]) {
if (--inDegree[w] == 0) {
s.push(w);
}
}
}
return count == items.size();
}
/*!
*
* @param graph 图,邻接表
* @param inDegree 顶点入度
* @param items 待排序顶点
* @return 拓扑排序结果
*/
vector<int>
topSort_useQueue(const vector<vector<int>> &graph, vector<int> &inDegree, const vector<int> &items) {
// 找出入度为0的点开始遍历
std::queue<int> s;
for (auto i : items) {
if (inDegree[i] == 0) {
s.push(i); // 顶点入度为0,入栈
}
}
// 拓扑排序
vector<int> res;
while (!s.empty()) {
int v = s.front();
res.push_back(v); // 保存拓扑排序结果
s.pop();
for (auto w : graph[v]) {
if (--inDegree[w] == 0) {
s.push(w);
}
}
}
return res;
}
public:
// 考察判断有向图中是否有环
// 方法一,拓扑排序判断是否有环,得到拓扑排序结果后校验节点数是否和原节点数一致
bool canFinish_1e(int numCourses, vector<vector<int>> &prerequisites) {
vector<int> inDegree(numCourses, 0); // 顶点入度
vector<vector<int>> graph(numCourses, vector<int>()); // 图,邻接表存储
// 根据先决条件,计算每个点的入度,同时构造图
for (auto &p : prerequisites) {
++inDegree[p[0]];
graph[p[1]].push_back(p[0]);
}
vector<int> items(numCourses, 0);
for (int i = 0; i < numCourses; ++i) {
items[i] = i;
}
// auto res = topSort_useStack(graph, inDegree, items);
auto res = topSort_useQueue(graph, inDegree, items);
return res.size() == numCourses; // 拓扑排序后的点数量等于图原始点数量,则没有环,否则<原始点数量证明有环
}
// 方法二,拓扑排序判断是否有环,不关注拓扑排序结果
bool canFinish_2e(int numCourses, vector<vector<int>> &prerequisites) {
vector<int> inDegree(numCourses, 0); // 顶点入度
vector<vector<int>> graph(numCourses, vector<int>()); // 图,邻接表存储
// 根据先决条件,计算每个点的入度,同时构造图
for (auto &p : prerequisites) {
++inDegree[p[0]];
graph[p[1]].push_back(p[0]);
}
vector<int> items(numCourses, 0);
for (int i = 0; i < numCourses; ++i) {
items[i] = i;
}
return topSortJudgeCircle(graph, inDegree, items);
}
// 方法三,利用dfs深度优先遍历判断有向图是否存在环
bool canFinish(int numCourses, const vector<vector<int>> &prerequisites) {
vector<vector<int>> graph(numCourses, vector<int>()); // 图,邻接表存储
// 根据先决条件,计算每个点的入度,同时构造图
for (auto &p : prerequisites) {
graph[p[1]].push_back(p[0]);
}
// 辅助数据初始化
visited.assign(numCourses, 0);
while (!sortRes.empty()) {
sortRes.pop();
}
valid = true;
// dfs遍历
for (int i = 0; i < numCourses; ++i) {
if (visited[i] == 0) {
dfs(graph, i);
if (!valid) {
return false;
}
}
}
return true;
}
private:
vector<int> visited; // 三种状态,0未搜索, 1搜索中(邻接节点未搜索完成), 2搜索完
std::stack<int> sortRes; // 深度优先搜索中利用栈存储拓扑排序结果
bool valid = true;
void dfs(const vector<vector<int>> &graph, int v) {
visited[v] = 1;
for (int w : graph[v]) {
if (visited[w] == 1) {
// 如果遍历到状态为1的点,则证明该有向图中存在环
valid = false;
return;
}
if (visited[w] == 0) {
dfs(graph, w);
}
}
visited[v] = 2;
sortRes.push(v);
}
};