就比如最短路径和最小生成树,这两个问题相关算法当时看了快一个月,但等好不容易遇到相关问题时,偏偏还是给搞错了。
但图的题就相对来说偏套路一点,个人觉得时常把代码拿出来温习一下就得了,但考到的概率是真的不高。(除了拓扑排序)
所以本文主要是以课程表一题为例,讲解一下拓扑排序的两种基本思路。
207.课程表 medium
你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]
给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?
分析:此题主要是判断图是否是有向无环图。
可以通过深度优先遍历与广度优先遍历进行判断。
1. 深度优先遍历
遍历方法:借助visit数组进行遍历判断
visit[i] = 0时,未访问到
visit[i] = 1时,表示已经被别的节点访问到了,可以直接跳过
visit[i] = -1时,表示在本轮DFS中被第二次访问到,即该图中有环,return false。
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses, vector<int>());
vector<int> visit(numCourses);
for (auto a : prerequisites) {
graph[a[1]].push_back(a[0]);
}
for (int i = 0; i < numCourses; ++i) {
if (!canFinishDFS(graph, visit, i)) return false;
}
return true;
}
bool canFinishDFS(vector<vector<int>>& graph, vector<int>& visit, int i) {
if(visit[i] == -1) return false;
if(visit[i] == 1) return true;
visit[i] = -1;
for(auto a : graph[i]) {
if (!canFinishDFS(graph, visit, a)) return false;
}
visit[i] = 1;
return true;
}
};
2. 广度优先遍历
需要借助一个队列,将所有入度为0的入队。
如果队列不为空,则队列节点出队,将所有该节点的邻接节点入度-1, 如果为0则入队。
当队列为空时,判断节点是否遍历完,若未遍历完,则该图有环。
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses, vector<int>());
vector<int> in(numCourses);
for (auto a : prerequisites) {
graph[a[1]].push_back(a[0]);
++in[a[0]];
}
queue<int> q;
for (int i = 0; i < numCourses; ++i) {
if(in[i] == 0) q.push(i);
}
while(!q.empty()) {
int t = q.front(); q.pop();
for(auto a : graph[t]) {
--in[a];
if(in[a] == 0) q.push(a);
}
}
for (int i = 0; i < numCourses; ++i) {
if(in[i] != 0) return false;
}
return true;
}
};
我个人常用广度优先遍历,但深度优先遍历的三种visit形式其实也很好用。