• 拓扑排序之课程表问题


    1. 题目描述

    你这个学期必须选修 numCourses 门课程,记为0numCourses - 1

    在选修某些课程之前需要一些先修课程。 先修课程按数组prerequisites 给出,其中prerequisites[i] = [ai, bi] ,表示如果要学习课程ai 则 必须 先学习课程bi

    • 例如,先修课程对[0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1
      请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false
    输入:numCourses = 2, prerequisites = [[1,0]]
    输出:true
    解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
    
    输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
    输出:false
    解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
    

    2.解题思路

    2.1 广度优先 根据入度拓补排序

    1. 统计课程安排图中每个节点的入度,生成 入度表 indegrees
    2. 借助一个队列 queue,将所有入度为 0 的节点入队。
    3. 当 queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre:
    • 并不是真正从邻接表中删除此节点 pre,而是将此节点对应所有邻接节点 cur 的入度 -1,即 indegrees[cur] -= 1
    • 当入度 -1后邻接节点 cur 的入度为 0,说明 cur 所有的前驱节点已经被 “删除”,此时将 cur 入队。
    1. 在每次 pre 出队时,执行 numCourses--
    • 若整个课程安排图是有向无环图(即可以安排),则所有节点一定都入队并出队过,即完成拓扑排序。换个角度说,若课程安排图中存在环,一定有节点的入度始终不为 0
    • 因此,拓扑排序出队次数等于课程个数,返回 numCourses == 0 判断课程是否可以成功安排。

    时间复杂度 O(N + M)

    空间复杂度 O(N + M)

    2.2 深度优先 判断图中是否有环

    1.借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:

    • 未被 DFS 访问:i == 0;
    • 已被其他节点启动的 DFS 访问:i == -1;
    • 已被当前节点启动的 DFS 访问:i == 1。

    2.对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 False。

    3.当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1 并返回 True。
    若整个图 DFS 结束并未发现环,返回 True。

    3. 代码实现

    一般这种问题上来先生成邻连表, 后续问题处理起来比较方便

    // 1. 广度优先搜索 入度表
    func canFinish(numCourses int, prerequisites [][]int) bool {
    	// 邻连表
    	adjacency := make([][]int, numCourses)
    	// 入度表
    	indegrees := make([]int, numCourses)
    	for _, prerequisite := range prerequisites {
    		indegrees[prerequisite[1]]++
    		adjacency[prerequisite[1]] = append(adjacency[prerequisite[1]], prerequisite[0])
    	}
    	queue := []int{}
    	for i := 0; i < numCourses; i++ {
    		if indegrees[i] == 0 {
    			queue = append(queue, i)
    		}
    	}
    	for len(queue) > 0 {
    		node := queue[0] // 出队
    		queue = queue[1:]
    		numCourses--
    		// 将队头节点相连的节点的入度-1
    		for i := 0; i < len(adjacency[node]); i++ {
    			indegrees[adjacency[node][i]]--
    			// 入度为0加入队列
    			if indegrees[adjacency[node][i]] == 0 {
    				queue = append(queue, adjacency[node][i])
    			}
    		}
    	}
    	return numCourses == 0
    }
    
    
    // 2. 深度优先搜索 判断图中是否有环
    func canFinish(numCourses int, prerequisites [][]int) bool {
    	adjacency := make([][]int, numCourses)
    	flag := make([]int, numCourses)
    	for _, prerequisite := range prerequisites {
    		adjacency[prerequisite[1]] = append(adjacency[prerequisite[1]], prerequisite[0])
    	}
    	for i := 0; i < numCourses; i++ {
    		if !dfs(adjacency, flag, i) {
    			return false
    		}
    	}
    	return true
    }
    func dfs(adjacency [][]int, flag []int, i int) bool {
    	if flag[i] == -1 {
    		return true
    	}
    	if flag[i] == 1 {
    		return false
    	}
    	flag[i] = 1
    	for j := 0; j < len(adjacency[i]); j++ {
    		if !dfs(adjacency, flag, adjacency[i][j]) {
    			return false
    		}
    	}
    	flag[i] = -1
    	return true
    }
    

    leetcode 207. 课程表

    参考链接

  • 相关阅读:
    第三百节,python操作redis缓存-其他常用操作,用于操作redis里的数据name,不论什么数据类型
    第二百九十九节,python操作redis缓存-SortSet有序集合类型,可以理解为有序列表
    第二百九十八节,python操作redis缓存-Set集合类型,可以理解为不能有重复元素的列表
    第二百九十七节,python操作redis缓存-List类型,可以理解为列表
    第二百九十六节,python操作redis缓存-Hash哈希类型,可以理解为字典类型
    第二百九十五节,python操作redis缓存-字符串类型
    第二百九十四节,Redis缓存-Redis安装
    第二百九十三,Memcached缓存
    第二百九十二节,RabbitMQ多设备消息队列-Python开发
    数据库大小写与编程语言大小写的区别
  • 原文地址:https://www.cnblogs.com/lsyy2020/p/14637980.html
Copyright © 2020-2023  润新知