拓扑排序简介:
-
使用的算法思想:
广度优先遍历、贪心算法 -
应用场景:有前置条件的。任务调度计划、课程安排,eg:《机器学习》的先导课程为《高等数学》;
-
作用:
-
得到一个
拓扑序,但不唯一。 -
检测有向图是否有环。
- 补充:
无向图,检测是否有环,使用的数据结构是并查集。
- 补充:
-
207. Course Schedule 课程表1
有两个解法、分别是BFS(拓扑排序、结合贪心),DFS(老方法)
拓扑排序:
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
ArrayList<Integer>[] graph = new ArrayList[numCourses];
int[] inDegree = new int[numCourses];
// inDegree 用来存储每个节点的入度。模拟删除节点。 c++代码中,有将这个封装进node里面
for (int i = 0; i < numCourses; i++) {
graph[i] = new ArrayList<>();
}
// build graph;
for (int[] edge : prerequisites) {
int from = edge[1];
int to = edge[0];
graph[from].add(to);
inDegree[to]++;
}
int count = 0; // 利用count 去计算是否全部 结点已经变为 0 入度
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int peek = queue.poll();
count++;
for (int v : graph[peek]) {
inDegree[v]--;
if (inDegree[v] == 0) {
queue.offer(v);
}
}
}
if (count == numCourses) {
return true;
}
return false;
}
}
DFS
class Solution {
private List<List<Integer>> edges;
private boolean[] visited;
private boolean[] onPath;
private boolean hasCycle;
public boolean canFinish(int numCourses, int[][] prerequisites) {
visited = new boolean[numCourses];
onPath = new boolean[numCourses];
edges = new ArrayList<>(numCourses);
for (int i = 0; i < numCourses; i++) {
edges.add(new ArrayList<>());
}
for (int[] edge : prerequisites) {
edges.get(edge[1]).add(edge[0]);
}
for (int i = 0; i < numCourses; i++) {
dfs(i);
}
return !hasCycle;
}
public void dfs(int s) {
if (onPath[s]) {
hasCycle = true;
return;
}
if (visited[s]) return;
visited[s] = true;
onPath[s] = true;
for (int i = 0; i < edges.get(s).size(); i++) {
dfs(edges.get(s).get(i));
}
onPath[s] = false;
}
}
210. 课程表 II
广度优先遍历、贪心算法
class Solution {
public int[] findOrder(int numCourses, int[][] prerequisites) {
ArrayList<Integer>[] graph = new ArrayList[numCourses];
for (int i = 0; i < numCourses; i++) {
graph[i] = new ArrayList<>();
}
// 维护一个入度的数组。 // 用来判断 结点入度为0时 可以加入队列。
int[] inDegree = new int[numCourses];
for (int[] v : prerequisites) {
int from = v[1];
int to = v[0];
graph[from].add(to);
inDegree[to]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int[] res = new int[numCourses];
int count = 0;
while (!queue.isEmpty()) {
int now = queue.poll();
res[count] = now;
count++;
for (int i = 0; i < graph[now].size(); i++) {
inDegree[graph[now].get(i)]--;
if (inDegree[graph[now].get(i)] == 0) {
queue.offer(graph[now].get(i));
}
}
}
if (count == numCourses) {
return res;
}
return new int[0];
}
}
先说最重要的部分:
- 「拓扑排序」是专门应用于有向图的算法;
- 这道题用 BFS 和 DFS 都可以完成,只需要掌握 BFS 的写法就可以了,BFS 的写法很经典;
- BFS 的写法就叫「拓扑排序」,这里还用到了贪心算法的思想,贪的点是:当前让入度为 0 的那些结点入队;
- 「拓扑排序」的结果不唯一;
- 删除结点的操作,通过「入度数组」体现,这个技巧要掌握;
- 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环,这个知识点非常重要,如果在面试的过程中遇到这个问题,要把这一点说出来。
- 具有类似附加功能的算法还有:Bellman-Ford 算法附加的作用是可以用于检测是否有负权环(在这里不展开了,我也不太熟)。