拓扑排序—图
1 简介
关于拓扑排序是这样定义的:
给定一个包含n个节点的有向图G,我们给出它的节点编号的一种排列,如果满足: 对于图G中的任意一条有向边 (u, v),u在排列中都出现在 v的前面。 那么称该排列是图G的拓扑排序。
若有向图中存在环路,则图G的拓扑排序不存在。原因是,有图G(u,v)(v,u),形成环路。写出遍历序列是u, v, u; 根据拓扑排序的定义,有向边(u,v)需要满足u,在v前,根据获得的排序结果u, v, u可见,存在环路的有向图不存在拓扑排序。
拓扑排序还有一个特点就是,排序的结果不唯一,极端的情况就是当有向图G中的节点没有一条边时,排序结果可以是任何顺序。
具体正确的有向图拓扑排序可见下节实现的例子。
2 实现思想
2.1 深度优先遍历(DFS)
深度优先的思想就是不断的向子节点遍历下去,直到子节点为空为止。当我们使用dfs实现拓扑排序时,需要为当前节点设置3个状态,分别表示当前节点还未搜索过、当前节点正在搜索,以及当前节点已经搜索完毕。
根据父节点在子节点前,以及深度优先的特点,我们要实现有向图的拓扑排序,可以采用栈来保存已经遍历过的节点。这样能够保证已经遍历过的节点总在栈底,父节点总在其上方,出栈时可以保证父节点在子节点前。
当我们明确采用dfs遍历图,以及采用栈保存已搜索的节点后,我们可以来理以下实现的思路。
1、从图中任一个节点开始遍历,将该节点状态置为正在搜索中,若当前节点存在一条边指向其它节点,则搜索当前指向的节点,重复上述过程直到一个节点没有子节点为止。
2、判断该节点是否已经被搜索过,若没有则将该节点状态置为已搜索,并将节点入栈。
3、若当前节点状态为正在搜索,说明遇到了环路(在步骤一已经将父节点置为正在搜索),直接返回该有向图不存在拓扑排序。
下面使用一个例子说明上述步骤:
存在有向图G如下:
1、从A节点开始遍历,使用黄色表示当前节点正在搜索,使用绿色表示已搜索,使用白色表示为搜索。A节点存在子节点,继续搜索其子节点(任选一个子节点即可)
2、我们选择搜D节点,D节点存在子节点,继续遍历子节点。
3、遍历到E节点,E节点已经不存在子节点,故将其入栈,并将状态置为已搜索。
4、回溯到D节点,D节点已不存在子节点未遍历,故将D入栈。
5、回溯到A节点,A节点存在子节点未遍历,继续遍历其子节点。
6、节点C不存在子节点,将其入栈。
7、回溯到A节点,A节点不存在子节点未遍历,故将A入栈。
8、遍历到A的兄弟节点,B不存在未遍历的子节点,故将B入栈。
最终得到的拓扑排序是:BACDE。
2.2 广度优先遍历(BFS)
广度优先搜索实现的拓扑排序过程,与深度优先搜索实现的过程刚好相反。深度优先搜索是将后面的节点放入栈底,广度优先搜索则是将前面的节点线全部遍历,在遍历后面的节点。所有广度优先搜索使用的是队列来实现拓扑排序。
其具体思想与实现步骤如下:
1、将所有入度为0即没有父节点指向的节点加入结果。
2、将刚刚加入结果的节点,所有边都删除,继续寻找入度为0的节点。
3、重复以上所有两个步骤,直到结果中节点数等于图的节点数,若不等,则说明该图洲存在环路。
还是使用上述例子说明bfs的流程:
存在有向图G如下:
1、将所有入度为0的节点加入队列。
2、将队首元素出队,加入结果中,并且将其所有边删除。
3、再次将当前所有入度为0的节点加入队列。
4、队首元素再次出队,删除对于的边。
5、再次将入度为0的节点入队
6、再次出队
7、再次入队
8、省略几步,直接全部出队,结果是:BADCE
用递归可以很容易实现该过程。接下来我们使用leetcode上的一道题说明,拓扑排序可以解决怎样的问题,以及给出拓扑排序的代码实现。
3 课程表问题
课程表问题,原题在LeetCode.210, 题意如下:
现在你总共有 n 门课需要选,记为 0 到 n-1。
在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]
给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。
可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组。
输入: 2, [[1,0]] 输出: [0,1] 解释: 总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。 输入: 4, [[1,0],[2,0],[3,1],[3,2]] 输出: [0,1,2,3] or [0,2,1,3] 解释: 总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
经过我们对拓扑排序的学习,思路应该很清晰,就是节点的拓扑排序问题。当存在环路是直接返回一个空数组即可。我们把先修课程作为父节点,指向后修课程,将每个课程这样处理后就形成一个有向图。
3.1DFS 代码实现
使用栈辅助