通常的解题思路
拓扑排序是解决有向无环图(DAG)中节点的线性排序问题,使得图中的每一条有向边均从排在前面的节点指向排在后面的节点。下面是拓扑排序的一般解题思路:
-
构建图: 将给定的有向图表示为数据结构,通常使用邻接表或邻接矩阵。
-
计算入度: 对于每个节点,计算它的入度,即指向该节点的边的数量。入度为0的节点是拓扑排序的起点。
-
选择起点: 从入度为0的节点中选择一个作为起点,将其加入结果列表。
-
更新入度: 移除起点节点以及从它出发的所有边,更新剩余节点的入度。
-
重复步骤3-4: 重复选择起点和更新入度的步骤,直到所有节点都被加入结果列表。
-
检查图的合法性: 如果最终结果列表的长度等于图中的节点数,说明图是有向无环图(DAG),否则存在环,无法进行拓扑排序。
这个算法的关键在于不断选择入度为0的节点,并更新图的结构,直到所有节点都被遍历。如果最终得到的排序与图中节点的数量相等,那么拓扑排序是成功的。
拓扑排序常用于任务调度、依赖关系分析等场景,其中任务之间存在一定的先后顺序。
有哪些经典问题:
-
课程表: 给定课程数和它们的先修关系,判断是否可以完成所有课程,并返回一种可行的学习顺序。
-
任务调度: 给定任务和它们之间的依赖关系,找到一个合理的执行顺序,使得所有任务都能完成。
-
项目管理: 在软件开发等项目中,任务之间存在依赖关系,拓扑排序可以用来确定任务的执行顺序。
-
工作流程: 对于工作流程中的各个步骤,拓扑排序可以帮助确定它们的执行顺序。
-
文件依赖关系: 在构建系统中,文件之间可能存在依赖关系,拓扑排序可用于构建文件的顺序。
-
包依赖关系: 在软件开发中,不同模块或库之间存在依赖关系,拓扑排序用于确定它们的编译或加载顺序。
eg: 课程表是否能修完的问题
你总共有 numCourses
门课需要选,编号为 0
到 numCourses-1
。有一些课程可能有先修课程,例如,想要学习课程 0,你需要先完成课程 1,我们用一个先修课程的关系列表表示。
返回整个可以学完的课程顺序。如果有多个可能的顺序,你可以返回任何一个合法的顺序。如果不可能完成所有课程,返回一个空数组。
示例:
输入: numCourses = 2, prerequisites = [[1,0]]
输出: [0,1]
或 [1,0]
解释: 你必须先修课程 1,然后才能学习课程 0。
解题思路:
-
构建图: 使用邻接表表示图,用数组
inDegree
记录每个节点的入度。 -
计算入度: 遍历先修课程关系,计算每门课的入度。
-
拓扑排序: 使用队列进行拓扑排序。将入度为0的节点加入队列,并依次弹出队列的节点,更新其邻居节点的入度。重复这一过程直到队列为空。
-
检查结果: 如果拓扑排序后的节点数等于课程数,说明可以完成所有课程;否则,无法完成。
这是一个经典的拓扑排序问题,拓扑排序的思想是从有向图中选择一个入度为0的节点,输出它,并移除它以及以它为起点的边,重复这个过程直到所有节点都被输出。如果最终输出的节点数量等于图中的节点数量,说明图是有向无环图(DAG),可以进行拓扑排序。
import java.util.*;
public class CourseSchedule {
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 构建图的邻接表表示
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
// 记录每个节点的入度
int[] inDegree = new int[numCourses];
// 构建图和入度数组
for (int[] pair : prerequisites) {
int course = pair[0];
int prerequisite = pair[1];
graph.get(prerequisite).add(course);
inDegree[course]++;
}
// 使用队列进行拓扑排序
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int current = queue.poll();
numCourses--;
for (int neighbor : graph.get(current)) {
if (--inDegree[neighbor] == 0) {
queue.offer(neighbor);
}
}
}
return numCourses == 0;
}
public static void main(String[] args) {
CourseSchedule courseSchedule = new CourseSchedule();
int numCourses = 4;
int[][] prerequisites = {{1,0},{2,0},{3,1},{3,2}};
System.out.println(courseSchedule.canFinish(numCourses, prerequisites)); // 输出 true
}
}