LeetCode 207:课程表(拓扑排序判断是否成环)

这篇博客介绍了如何使用深度优先搜索(DFS)和广度优先搜索(BFS)解决LeetCode上的第207题——课程排序。题目要求确定是否存在一个课程学习顺序,使得所有课程都可以完成,避免循环依赖。作者提供了两种算法的Java实现,一种是基于DFS,另一种是基于BFS。在DFS中,通过标记和路径状态来检测环;在BFS中,利用入度数组和队列进行遍历。两种方法都有效地解决了问题。
摘要由CSDN通过智能技术生成

LeetCode 207

题目:
在这里插入图片描述

方法一: DFS

看到依赖问题,首先想到把问题转换为有向图

  1. 利用条件构建图,课程即顶点,几个节点就有几个顶点,以个数建立List[ ]数组表示图,数组的索引就是顶点的值,数组的每个元素都是顶点对应的邻接表;
    用先学习的课程pre指向后学习的课程follow来表示有向图的顺序
  2. main中用第一个for遍历图的每个顶点,对顶点的邻接表使用dfs
  3. 在dfs中用第二个for遍历该顶点的邻接表
    当onpath为真则标记isCycle为true
    注意for结束后要还原 onpath=false

注意:

  1. graph[i][j ]即邻接表,i 就是课程的编号; graph[i] 就是i 课程指向的所有课;
  2. 为便于记忆,当onpath[s]=true 和 marked[s]=true 都直接return
  3. onPath[s]在marked[s]之前 !
  4. new泛型数组后面不需要再加 <>:
    List<Integer>[] graph=new List<Integer>[3]; X
    List<Integer>[] graph=new List[3];

Java实现:

class Solution {
    boolean[] marked;
    boolean[] onPath;
    boolean isCycle=false;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 不可能完成: 即存在循环依赖,互相为前提, 就不能完成课程
        //构建图
        List<Integer>[] graph=buildGraph(numCourses,prerequisites);
        //初始化
        marked=new boolean[numCourses];
        onPath=new boolean[numCourses];
    
        //用dfs遍历每个顶点
        for(int i=0;i<numCourses;i++){
            dfs(graph,i);           
        }
        return !isCycle;//不成环即可以完成学习
    }
    	
        List<Integer>[] buildGraph(int numCourses,int[][]prerequisites){
            List<Integer>[] graph=new List[numCourses];//new泛型数组后面不需要再加 <>
            //初始化每个元素:new一个List作为邻接表
            for(int i=0;i<numCourses;i++){
                graph[i]=new LinkedList<>();
            }
            //添加有向边
            for(int[] k:prerequisites){
                int pre=k[1];
                int follow=k[0];
                graph[pre].add(follow); // graph的索引对应顶点的值,
            }
            return graph;
        }
    }
    
        void dfs(List<Integer>[] graph,int s){
            //终止条件 onPath[s]在marked[s]之前 !
            if(onPath[s]==true){
                isCycle=true;
                return;
            }
            if(marked[s]==true){
                return;
            }
            marked[s]=true;//标记当前s
            onPath[s]=true;//标记onpath
            //遍历邻接表
            for(int k:graph[s]){
                    dfs(graph,k);
            }
            onPath[s]=false;//onpath还原
        }

或者将判断marked和onPath放到遍历邻接表的for中

        void dfs(List<Integer>[] graph,int s){
            //终止条件 onPath[s]在marked[s]之前 !
            marked[s]=true;//标记当前s
            onPath[s]=true;//标记onpath
            //遍历邻接表
            for(int k:graph[s]){
                if(!marked[k]){
                    dfs(graph,k);
                }
                if(onPath[k]){
                    isCycle=true;
                    return; 
                }
            }
            onPath[s]=false;//onpath还原
        }

方法二:BFS

图的BFS需要借助入度数组 indegree , 用入读数组实现了marked[ ]的作用,只有入度为 0 的节点才能入队,从而保证不会出现死循环
当元素入度为0才能进入nodes队列
开始执行 BFS 循环,不断弹出队列中的节点,减少相邻节点的入度,并将入度变为 0 的节点加入队列;
while内一个for就是一层;
图的BFS在for外面弹出!而二叉树的BFS在for里面弹出node !
这里的一个for遍历的是temp的邻接表,也就是temp下面这一层,入度为0后的点再加入nodes,和二叉树BFS类似 !
如果最终所有节点都被遍历过(count 等于节点数),则说明不存在环,反之则说明存在环

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        List<Integer>[] graph=buildGraph(prerequisites,numCourses);
        //入度数组
        int[] indegree=new int[numCourses]; //int数组会自动初始化为0
        for(int[] k:prerequisites){
            int from=k[1]; //先修课程from
            int to=k[0]; // 后续课程to即被指的元素,from指向to,to被指的次数即入度
            indegree[to]++;  //入度数量++
        }

        //如果入度为0,才可以作为BFS的起点 !
        Queue<Integer> nodes=new LinkedList<>();
        for(int i=0;i<numCourses;i++){
            if(indegree[i]==0){
                nodes.add(i);
            }
        }
        int count=0;
        //BFS
        //start是入度为0的点,即不被依赖
        //没有额外的target,nodes遍历完则结束
        while(!nodes.isEmpty()){
            int temp=nodes.poll(); // 1. 弹出节点
            count++;
            for(int k :graph[temp]){ // 遍历temp的邻接表的每个元素,相当于遍历temp节点下面这一层 !
                indegree[k]--;
                if(indegree[k]==0){ //2. 当邻接表中有元素入度为0了,添加节点
                    nodes.add(k);
                }
            }
        }
        return count==numCourses; //如果所有节点都被遍历了一次则不成环
        }


    List<Integer>[] buildGraph(int[][] prerequisites,int numCourses){
        List<Integer>[] graph=new List[numCourses];
        for(int i=0;i<numCourses;i++){
            graph[i]=new LinkedList<Integer>();
        }
        for(int[] k:prerequisites){
            int pre=k[1];
            int follow=k[0];
            graph[pre].add(follow);
        }
        return graph;
    }   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值