• 【LeetCode】课程表(图论判环 拓扑排序/dfs)


    课程表( 拓扑排序/dfs 判环)

    题目链接:https://leetcode-cn.com/problems/course-schedule/

    题目大意:给定一个课程依赖关系图,比如课程A依赖课程B,课程B依赖课程C,按照上述的依赖关系,能否学习完所有的课程?

    先学C,再学B,最后学A即可

    方式1:拓扑排序

    我们按照图论的思想,将每个课程看做一个节点,将课程间的这种依赖和被依赖的关系看做节点的出度和入度,即

    依赖:出度

    被依赖:入度

    样例如下

    A依赖B等价于A节点指向B节点,那么A节点的出度是1,B节点的入度是1

    A依赖D等价于A节点指向B节点,那么A节点现在的出度是2,D节点的入度是1

    我们首先找到所有入度为0的节点,即这些节点不被任何节点依赖,我们可以先完成这些课程nums1,这些课程被完成后,依赖这些课程的节点nums2其入度也需要被修改,具体的修改操作为nums2节点的入度都需要减去1,因为nums1节点课程都完成了

    然后我们继续选取入度为0的点,直到没有入度为0的点

    对于这些入度为0的点的存储我们可以采用队列,先进先出

    当队列中没有元素后,即所有入度为0的点都被处理了,如果处理的点总数等于节点总数,那么证明可以存在一个拓扑顺序,学习完所有课程,如果不等于,那么证明图中存在环,互相依赖死循环了,就不能学习完所有课程

    这种方法叫做拓扑排序,出入队列元素的先后顺序就是拓扑排序的顺序

    时间复杂度:O(n+m)

    • n 为课程数,m 为先修课程的要求数。这其实就是对图进行广度优先搜索的时间复杂度。

    空间复杂度:O(n+m)

    • 双层map存储图需要O(m),队列存储需要O(n)
    
    func canFinish(numCourses int, prerequisites [][]int) bool {
    	G:=make(map[int]map[int]int)
    
    	indegree:=make(map[int]int)
    
    	var queue []int
    
    	// 构建图和入度表
    	for i:=0;i<len(prerequisites);i++{
    		v1:=prerequisites[i][0]
    		v2:=prerequisites[i][1]
    		if _,ok:=G[v1];!ok{
    			G[v1]=make(map[int]int)
    		}
    		G[v1][v2]=1
    		indegree[v2]++
    	}
    
    	// 入度为0的点加入队列
    	for i:=0;i<numCourses;i++{
    		if indegree[i]==0{
    			queue=append(queue,i)
    		}
    	}
    
    	c:=0
    	for len(queue)!=0{
    		temp:=queue[0]
    		queue=queue[1:]
    
    		// 符合拓扑排序的节点数量
    		c++
    
    		// temp 和 i 存在边,则i的入度减1,如果i入度为0,则加入队列
    		for i:=0;i<numCourses;i++{
    			if G[temp][i]==1{
    				indegree[i]--
    				if indegree[i]==0{
    					queue=append(queue,i)
    				}
    			}
    		}
    
    	}
    
    	// 符合拓扑排序节点的数量等于节点总数量,证明不存在环,符合要求
    	if c==numCourses{
    		return true
    	}
    	return false
    
    }
    

    方式2:dfs

    拓扑排序的方法是基于入度为0的点考虑的,其实我们也可以基于出度为0的点考虑

    出度为0意味着此节点不依赖其他节点,只可能被其他节点依赖

    那如何寻找到出度为0的节点呢?

    答案就是深度优先搜索,dfs

    有路径就一直搜索下去,深度优先

    对于每个节点u,我们可以定义0,1,2三种状态

    • 0 未搜索

      • 代表此节点还没有被搜索过
    • 1 搜素中

      • 我们搜索过这个节点,但还没有回溯到该节点即该节点还没有入栈,还有相邻的节点没有搜索完成
    • 2 搜素已完成

      • 我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。

    我们搜素一个节点x时,会搜索x指向的所有节点

    • 如果这些节点没有被搜索过,那么搜索它
    • 如果这些节点处于搜索中,也就是被其他dfs搜索过了,那么证明此图中存在环!
    • 如果此节点处于搜索已完成,那么跳过

    时间复杂度:O(n+m), n是课程数量,m是依赖数量

    空间复杂度:O(n+m) 存储图需要O(m),递归需要O(n)

    var G map[int]map[int]int
    var flag [100005]int
    var N int
    var result bool
    
    func dfs(index int){
    	// index标记为已搜索,但是还在搜索和index有关的其他节点
    	flag[index]=1
    
    	for i:=0;i<N;i++{
    		if G[index][i]==1{
    			if flag[i]==0{
    				// i没有被搜索过,搜索i
    				dfs(i)
    				if result==false{
    					return
    				}
    			}else if flag[i]==1{
    				// i已经被其他dfs标记过了,证明存在环,没有合法拓扑排序
    				result=false
    				return
    			}else if flag[i]==2 {
    				continue
    				// i 此时已经在拓扑排序中,无需做任何操作
    			}
    		}
    	}
    
    	// index标记为加入到了拓扑排序中
    	flag[index]=2
    }
    func canFinish(numCourses int, prerequisites [][]int) bool {
    	G = make(map[int]map[int]int)
    	flag=[100005]int{}
    	N=numCourses
    	result=true
    
    	// 构建图
    	for i:=0;i<len(prerequisites);i++{
    		v1:=prerequisites[i][0]
    		v2:=prerequisites[i][1]
    		if _,ok:=G[v1];!ok{
    			G[v1]=make(map[int]int)
    		}
    		G[v1][v2]=1
    	}
    
    
    	// 对每一个没有被搜索过的节点都进行一次搜索
    	for i:=0;i<numCourses;i++{
    		if flag[i]==0{
    			dfs(i)
    		}
    	}
    
    	return result
    }
    
  • 相关阅读:
    BZOJ 3754 Tree之最小方差树
    【CSS】318- CSS实现宽高等比自适应容器
    【Vuejs】317- 提升90%加载速度——Vuecli下的首屏性能优化
    【H5】316- 移动端H5跳坑指南
    【每周小回顾】2- 一起回顾上周精彩内容
    【Webpack】315- 手把手教你搭建基于 webpack4 的 vue2 多页应用
    【Web技术】314- 前端组件设计原则
    【JS】313- 复习 回流和重绘
    【JS】312- 复习 JavaScript 严格模式(Strict Mode)
    【Canvas】311- 解决 canvas 在高清屏中绘制模糊的问题
  • 原文地址:https://www.cnblogs.com/yinbiao/p/16111784.html
Copyright © 2020-2023  润新知