拓扑排序的解题思路

通常的解题思路

拓扑排序是解决有向无环图(DAG)中节点的线性排序问题,使得图中的每一条有向边均从排在前面的节点指向排在后面的节点。下面是拓扑排序的一般解题思路:

  1. 构建图: 将给定的有向图表示为数据结构,通常使用邻接表或邻接矩阵。

  2. 计算入度: 对于每个节点,计算它的入度,即指向该节点的边的数量。入度为0的节点是拓扑排序的起点。

  3. 选择起点: 从入度为0的节点中选择一个作为起点,将其加入结果列表。

  4. 更新入度: 移除起点节点以及从它出发的所有边,更新剩余节点的入度。

  5. 重复步骤3-4: 重复选择起点和更新入度的步骤,直到所有节点都被加入结果列表。

  6. 检查图的合法性: 如果最终结果列表的长度等于图中的节点数,说明图是有向无环图(DAG),否则存在环,无法进行拓扑排序。

这个算法的关键在于不断选择入度为0的节点,并更新图的结构,直到所有节点都被遍历。如果最终得到的排序与图中节点的数量相等,那么拓扑排序是成功的。

拓扑排序常用于任务调度、依赖关系分析等场景,其中任务之间存在一定的先后顺序。

有哪些经典问题:

  1. 课程表: 给定课程数和它们的先修关系,判断是否可以完成所有课程,并返回一种可行的学习顺序。

  2. 任务调度: 给定任务和它们之间的依赖关系,找到一个合理的执行顺序,使得所有任务都能完成。

  3. 项目管理: 在软件开发等项目中,任务之间存在依赖关系,拓扑排序可以用来确定任务的执行顺序。

  4. 工作流程: 对于工作流程中的各个步骤,拓扑排序可以帮助确定它们的执行顺序。

  5. 文件依赖关系: 在构建系统中,文件之间可能存在依赖关系,拓扑排序可用于构建文件的顺序。

  6. 包依赖关系: 在软件开发中,不同模块或库之间存在依赖关系,拓扑排序用于确定它们的编译或加载顺序。

eg: 课程表是否能修完的问题

你总共有 numCourses 门课需要选,编号为 0numCourses-1。有一些课程可能有先修课程,例如,想要学习课程 0,你需要先完成课程 1,我们用一个先修课程的关系列表表示。

返回整个可以学完的课程顺序。如果有多个可能的顺序,你可以返回任何一个合法的顺序。如果不可能完成所有课程,返回一个空数组。

示例:

输入: numCourses = 2, prerequisites = [[1,0]]

输出: [0,1][1,0]

解释: 你必须先修课程 1,然后才能学习课程 0。

解题思路:

  1. 构建图: 使用邻接表表示图,用数组 inDegree 记录每个节点的入度。

  2. 计算入度: 遍历先修课程关系,计算每门课的入度。

  3. 拓扑排序: 使用队列进行拓扑排序。将入度为0的节点加入队列,并依次弹出队列的节点,更新其邻居节点的入度。重复这一过程直到队列为空。

  4. 检查结果: 如果拓扑排序后的节点数等于课程数,说明可以完成所有课程;否则,无法完成。

这是一个经典的拓扑排序问题,拓扑排序的思想是从有向图中选择一个入度为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
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值