环路检测+拓扑排序 2020-05-17(未经允许禁止转载)

1.图环路检测

  • 列表法。可以通过列表list记录己走过的路径,判断当前节点是否在已走过路径中
  • 字典法。可以通过一个字典记录所有节点的状态。要访问某节点时,先查字典看该节点是否已被访问
    字典法相对更优,墙裂推荐

2.有向无环图的拓扑排序

2.1有向无环图

有向无环图,常常被用来表示若干事件之间的前后依赖关系,用来规划任务的排期和调度

2.2什么是拓扑排序

给定一个包含 n 个节点的有向图 G,我们给出它的节点编号的一种排列,如果满足:

对于图 G 中的任意一条有向边 (u, v),u 在排列中都出现在 v 的前面

那么称该排列是图 G 的「拓扑排序」
【例如】


有向无环图拓扑排序图示(图片源于网络)

如图所示,排列ABFCDE为该图的一个拓扑排序,其中F的位置可以随意改变。拓扑排序是满足各个节点前后依赖关系的一种管理组织方式

2.3如何求解拓扑排序

求解拓扑排序,必须满足图中每个结点必须出现在其所有邻接结点的前面

对于图,通用的解法包括2种

  • DFS方法

2.3.1 DFS方法

DFS方法(深度优先搜索)只会访问每个顶点一次,又分为前中后序,这里肯定不用中序。前序和后序,选哪个?
上面提到,必须满足图中每个结点必须出现在其所有邻接结点的前面,这是啥?我们反过来看,让每个结点必须出现在其所有邻接结点的后面,这不就是后序遍历吗!我们把后序遍历的结果翻转过来,就是所求的拓扑排序

总结:一副有向无环图的拓扑排序即为所有顶点的逆后序排列

因此,在DFS后序遍历实现拓扑排序时,选用栈来保存顶点序列;保证在某顶点入栈前,其所有邻接点已入栈。假设我们当前搜索到了节点 u,如果它的所有邻节点都已经入栈,我们就可以把 u 入栈
DFS方法可视化:https://www.cs.usfca.edu/~galles/visualization/TopoSortDFS.html

  • BFS方法
    将入度为0的点称为顶点。顶点不会有任何入边,即没有任何前置依赖,我们可以直接选择加入结果集中。当我们将一个顶点加入答案中后,就可以马上移除它的所有出边,代表着它的相邻节点的该前置依赖解除。对剩下的图结构,重复上面的操作即可

3.例题

leetcode#### 210. 课程表 II

给2个参数,
numCourses,表示课程(图结点)数量
prerequisites, 如[[1,0],[2,0],[3,1],[3,2]]。二维数组中的每个元素可以表示为一条【有向边】,[1,0] 表示 0->1

求这个图的一个拓扑排序
思路:
1.DFS方法。使用【字典法】标记节点状态查看是否有环,使用【逆后序遍历】求拓扑排序

class Solution:
    def __init__(self):
        self.visited = set()
        self.stack = list()
        self.has_circle = False

    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        # 如果数组有环,则不可能完成
        # numCourses,表示课程(结点)数量
        # prerequisites, [[1,0],[2,0],[3,1],[3,2]]。二维数组中的每个元素可以表示为一条【有向边】
        # [1,0] 表示 0->1

        # 创建边字典, edges[i]表示起点为i的有向边,如{1:[2,3,4]}表示1的邻结点是2/3/4
        edges = dict()
        for i in range(numCourses):
            edges[i] = []
        
        # 填充边字典
        for bian in prerequisites:
            edges[bian[1]].append(bian[0])
        
        # 初始化状态字典
        state_dict = dict()
        for i in range(numCourses):
            state_dict[i] = 0

        for course in edges:
            if not self.has_circle:
                self.DFS(course, edges, state_dict)
            else:
                return []
            
        return self.stack[::-1]
    
    # 后序遍历
    def DFS(self, node, edges, state_dict):
        # 0表示未访问,1表示访问中,2表示访问完毕

        # 该节点未访问
        if state_dict[node] == 0:
            # 修改为访问中
            state_dict[node] = 1
            # 依次访问邻接节点
            for neibor in edges[node]:
                # 有环
                if state_dict[neibor] == 1:
                    self.has_circle = True
                    return
                elif state_dict[neibor] == 0:
                    self.DFS(neibor, edges, state_dict)

                # 注意,在每次循环最后检查是否有环
                if self.has_circle:
                    return
            
            # 邻接节点都搜索入栈完毕后,自己入栈
            self.stack.append(node)
            state_dict[node] = 2

2.使用BFS方法

class Solution:

    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        # 如果数组有环,则不可能完成
        # numCourses,表示课程(结点)数量
        # prerequisites, [[1,0],[2,0],[3,1],[3,2]]。二维数组中的每个元素可以表示为一条【有向边】
        # [1,0] 表示 0->1

        # 入度字典
        indegree = dict()
        for i in range(numCourses):
            indegree[i] = 0
        # 填充入度字典
        for edge in prerequisites:
            indegree[edge[0]] += 1
        
        # 邻接字典
        neibor_dict = dict()
        for i in range(numCourses):
            neibor_dict[i] = []
        # 填充邻接点字典
        for edge in prerequisites:
            neibor_dict[edge[1]].append(edge[0])

        # 根据入度,求顶点
        head_nodes = list()
        for node in indegree:
            if indegree[node] == 0:
                head_nodes.append(node)
        
        res = []
        # 当图有入度为0的顶点存在时
        while head_nodes:
            head = head_nodes.pop()
            res.append(head)
            # head邻接点的入度都-1
            for nb in neibor_dict[head]:
                indegree[nb] -= 1
                if indegree[nb] == 0:
                    head_nodes.append(nb)
        # 通过比较res的长度和节点数量来判断是否有环
        return res if len(res) == numCourses else []

你可能感兴趣的:(环路检测+拓扑排序 2020-05-17(未经允许禁止转载))