https://leetcode-cn.com/problems/course-schedule-ii
分析
这道题是典型的图论的算法题,主题就是拓扑排序:把图中的所有节点排序,使得对于所有的边<u, v>
,u在线性序列中出现在v之前。
一般来说,要做拓扑排序,有两种解法:
- 遍历图,找到所有入度为0的节点(源),然后删去这个节点以及从它出去的边,将该节点加入线性序列,重复这个过程,这属于BFS
- 使用DFS,对于所有尚未被搜索的u,当u没有对应的v或者v都已经被搜索完成时(可看作此时u的出度为0),将u加入线性序列,u标记为搜索完成。
BFS得到的序列是拓扑排序的正序,DFS得到的序列是拓扑排序的反序。
此外,如果图不是DAG,图中有环的话,是没有拓扑排序的,这很容易理解,而上面两种方法也有不同的判断是否有环的方法:
- 在BFS过程中,如果在剩下的节点集合中找不到源,剩余节点数又大于0,就有环
- 在DFS过程中,节点的标记不仅有尚未搜索和搜索完成,还有一种状态:搜索中,对于一个u,如果它的某个v处于搜索中,那么就遇到了环
注意
这两种解法都需要对一个节点u,快速获取到它的所有v,也即获取到从它出发的所有边。因此,事先建立用邻接表表示的图是很有必要的。
如果使用二维数组来实现,效率会高,但是会浪费很多空间;相比之下,二维的STL容器vector
就显得非常好用了,第一维表示u,第二维表示v,这就建立了邻接表。
解法一、BFS
我们首先要计算所有节点的入度in_degree,然后重复以下过程:
- 找到入度为0的节点,把它加入到线性序列的尾部
- 删除入度为0的节点,这在代码中体现为:对于所有从它到达的v,v的入度减一
- 找不到源时说明图中有环,不存在拓扑排序
如何快速获取到u的所有边我们已经在分析中阐述了,那么如何快速获取到入度为0的节点呢?我们可以用一个队列来保存所有入度为0的节点,每次取出一个,当v的入度减一变为0时,v又可以进入队列。这样,获取入度为0的节点仅需 O ( 1 ) O(1) O(1)的时间。
代码
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> in_degree(numCourses, 0);
vector<vector<int>> edges(numCourses, vector<int>());
for (int i = 0; i < prerequisites.size(); ++i) {
int v = prerequisites[i][0], u = prerequisites[i][1];
edges[u].push_back(v);
in_degree[v]++;
}
queue<int> q;
vector<int> order;
for (int i = 0; i < numCourses; ++i) if (in_degree[i] == 0) q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
order.push_back(u);
for (int i = 0; i < edges[u].size(); ++i) {
int v = edges[u][i];
if (--in_degree[v] == 0) q.push(v);
}
}
return order.size() == numCourses ? order : vector<int>();
}
};
注意代码中构造邻接表时我交换了u和v的定义,这是由于题目的特定要求,[1,0]
要求0输出在前,这说明其实构造的边u,v = 0,1
。
解法二、DFS
使用DFS的解法要更加简单一些,只是搜索中这个状态的定义非常重要,因为图中节点的入度大于1并不代表存在回路,只有当u尚未搜索完成,而从它深度搜索到的某个v又指向了它时,才存在回路。
代码
class Solution {
public:
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
vector<int> in_degree(numCourses, 0);
vector<vector<int>> edges(numCourses, vector<int>());
for (int i = 0; i < prerequisites.size(); ++i) {
int u = prerequisites[i][0], v = prerequisites[i][1];
edges[u].push_back(v);
in_degree[v]++;
}
vector<int> visited(numCourses, 0);
vector<int> order;
for (int i = 0; i < numCourses; ++i) {
if (visited[i] == 0) visited[i] = 1, dfs(i, visited, edges, order);
}
return acyclic ? order : vector<int>();
}
private:
bool acyclic = true;
void dfs(int u, vector<int>& visited, vector<vector<int>>& edges, vector<int>& order) {
for (int i = 0; i < edges[u].size(); ++i) {
int v = edges[u][i];
if (visited[v] == 1) {
acyclic = false;
return;
}
else if (visited[v] == 0) visited[v] = 1, dfs(v, visited, edges, order);
}
visited[u] = 2;
order.push_back(u);
}
};