拓扑排序是指在有向无环图中,把所有结点按照一定的排序排成一列,使得左边的点都指向右边的点。
一般有两种方法做拓扑排序
-
Kahn算法 / BFS广搜
用一个一维数组inDegrees记录每个结点的入度。再用一个数据结构q(随意都可以,数组,栈,列表等等)根据inDegrees表,找所有入度为0的结点存入。遍历q,一个一个pop出,每pop出一个要记录下来,把该结点指向的所有点的入度减1(更新入度表)。如果为更新后的点入度为0就存进q。再循环操作,直到q为空。如果路径长度不够或者入度表不是全0,说明该图不是有向无环图(可判断有向图是否有环)。
tips:如果是要打印字典序排序,就用优先列表正序存入度为0的结点。 -
DFS深搜 (后序遍历)
从任意一个未被访问的结点出发做深搜后序遍历。遍历所有结点,回溯前记录结点,最后路径再倒序一下就是正确的拓扑排序(或者建图的时候就把边的方向倒了,最后得到的排序不用倒)。如果有多个子图,要多次深搜,直到所有结点都被访问完(所有子图都搜完)得到多个子序列,再拼接一起就是答案。
tips:字典序排序想不到,哪位大神可以说说dfs的思路?如果单个子图字典序行得通,那么最后把各子序列归并一下就好了。
对于DFS方法有人可能疑惑为什么要回溯前才记录(要用后序遍历的原因),而且为什么可以从任一点开始?
- 能回溯的点说明已经把子代遍历完,确定是最后的了,于是可以记录下来,得到一个倒序记录。拓扑排序的一个节点可能有多个父结点,所以无法确定某点为先。如下图,如果我已遍历得0–>2顺序,但是在2之前还有一个点1,明显不对,先序遍历行不通。这是由结构所决定的,不同于树,拓扑排序分支节点之间可能有联系的,导致一个节点可能有多个父结点,故没有严格的先后顺序。而从后往前记录,因为节点遍历完分支,故不怕节点再指向任何节点也就不可能指回前面节点,后序遍历可行。如果要对一个二叉树做拓扑排序,代码与此大同小异,二叉树只有两个子结点,无需for循环而已。
- 从任意点开始遍历都行,是因为后序遍历保证了已记录的就是最靠后的了。后面记录的结点肯定不会后于已记录的结点。比如先从3开始搜,3->4(记录下4->3),然后无论从哪个结点再开始搜,都不会打乱顺序了,012都是先于3(可能的记录下为4->3->5->2->1->0->6->7),567放3前面也没事(可能的记录为4->3->6->7->5->2->1->0)。
样例图片
python代码:
from queue import PriorityQueue as pQueue
import sys
sys.setrecursionlimit(100000)
# ---------------------------方法1: Kahn方法 / BFS------------------------------
# 这是最常见的拓扑排序,0入度集合可以用队列,栈等等,只要能存储0入度的点就行
# 这里用了队列,其实就是跟广搜差不多,入度为0的才入队
def ts_bfs(inDegrees, graph):
n = len(graph)
result = []
q = []
vis = [False] * n
for i, inDegree in enumerate(inDegrees)<