概述
今天学习拓扑排序。如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图(Directed Acyclic Graph,DAG)。拓扑排序就是将有向无环图的所有顶点排序,使得图中任意两个点 u、v,只要存在边 u → v,那么拓扑排序中 u 一定在 v 前面。例如,高等数学、线性代数都可以直接开始学习,复变函数要学习完前两门课才能学习,所以,拓扑排序可以是:高等数学→线性代数→复变函数,或者线性代数→高等数学→复变函数。下面讲解具体算法的思路。
1.拓扑排序算法
基本思想如下:
- 定义一个队列 q,把所有入度为 0 的节点入队;
- 取队首节点,输出。删除它的出边,令出边到达的顶点入度减 1,如果该顶点入度变成 0,则将其加入队列;
- 反复进行第 2 步,直到队空。如果所有点都被访问,则 G 是有向无环图,否则有环。
代码如下:
List<List<Integer>> edges = new ArrayList<>(); // 邻接表
int[] inDegree = new int[n]; // 记录每个点的入度
public int[] topologicSort(int n, List<List<Integer>> edges, int[] inDegree) {
int[] ret = new int[n];
int num = 0; // 记录加入拓扑排序的定点数
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) { // 入度为0的节点入队
if (inDegree[i] == 0) q.offer(i);
}
while (!q.isEmpty()) {
int u = q.poll();
ret[num] = u;
for (int v : edges.get(u)) { // 取出后继节点
inDegree[v]--;
if (inDegree[v] == 0) {
q.offer(v);
}
}
edges.get(u).clear(); // 删除顶点u的所有出边
num++; // 加入拓扑排序的定点数加1
}
if (num != n) return new int[0]; // 排序失败
else return ret; // 排序成功
}
2.练习:力扣207. 课程表[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 。这是不可能的。
该题只需判断能否完成拓扑排序,即有没有环,解法如下:
class Solution {
private List<List<Integer>> edges;
private int[] inDegree;
public boolean canFinish(int numCourses, int[][] prerequisites) {
edges = new ArrayList<>(); // 邻接表
for (int i = 0; i < numCourses; i++) {
edges.add(new ArrayList<>());
}
inDegree = new int[numCourses]; // 记录入度
// 建图
for (int[] prerequisite : prerequisites) {
inDegree[prerequisite[0]]++;
edges.get(prerequisite[1]).add(prerequisite[0]);
}
return topologicSort(numCourses);
}
private boolean topologicSort(int n) {
int cnt = 0; // 记录参加过拓扑排序的节点数量
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
q.offer(i);
}
}
while (!q.isEmpty()) {
int cur = q.poll();
cnt++;
for (int next : edges.get(cur)) {
inDegree[next]--;
if (inDegree[next] == 0) {
q.offer(next);
}
}
}
return cnt == n;
}
}
3.练习:210. 课程表 II[2]
现在你总共有 numCourses
门课需要选,记为 0
到 numCourses - 1
。给你一个数组 prerequisites
,其中 prerequisites[i] = [ai, bi]
,表示在选修课程 ai
前 必须 先选修 bi
。
- 例如,想要学习课程
0
,你需要先完成课程1
,我们用一个匹配来表示:[0,1]
。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]
典型的拓扑排序题,在上一题基础上记录排序的结果即可:
class Solution {
public int[] findOrder(int numsCourses, int[][] prerequisites) {
List<List<Integer>> edges = new ArrayList<>(); // 邻接表
for (int i = 0; i < numsCourses; i++) {
edges.add(new ArrayList<>());
}
int[] inDegree = new int[numsCourses]; // 记录每个点的入度
for (int[] prerequisite : prerequisites) {
edges.get(prerequisite[1]).add(prerequisite[0]);
inDegree[prerequisite[0]]++; // 注意是入度
}
return topologicSort(numsCourses, edges, inDegree);
}
public int[] topologicSort(int n, List<List<Integer>> edges, int[] inDegree) {
int[] ret = new int[n];
int num = 0; // 记录加入拓扑排序的定点数
Queue<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) { // 入度为0的节点入队
if (inDegree[i] == 0) q.offer(i);
}
while (!q.isEmpty()) {
int u = q.poll();
ret[num] = u;
for (int v : edges.get(u)) { // 取出后继节点
inDegree[v]--;
if (inDegree[v] == 0) {
q.offer(v);
}
}
edges.get(u).clear(); // 删除顶点u的所有出边
num++; // 加入拓扑排序的定点数加1
}
if (num != n) return new int[0];
else return ret;
}
}
Reference
[1]力扣207. 课程表:https://leetcode-cn.com/problems/course-schedule/
[2]力扣210. 课程表 IIhttps://leetcode-cn.com/problems/course-schedule-ii/
欢迎关注。每天分享大数据开发面经和技术。
最后
以上就是朴素月饼为你收集整理的【算法】拓扑排序的全部内容,希望文章能够帮你解决【算法】拓扑排序所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复