¶概念
对于一个有向无环图 $G = (V, E)$ 来说,其 拓扑排序 是 $G$ 中所有结点的一种线性次序
该次序满足如下条件:如果图 $G$ 包括边 $(u, v)$,则结点 u 在拓扑排序中处于结点 $v$ 的前面
¶基本想法
本质上是一种依赖关系
给定一个 DAG(有向无环图),如果从 $i$ 到 $j$ 有边,则认为 $j$ 依赖于 $i$ 。如果 $i$ 到 $j$ 有路径( $i$ 可达 $j$ ),则称 $j$ 间接依赖于 $i$
拓扑排序的目标是将所有节点排序,使得排在前面的节点不能依赖于排在后面的节点
拓扑排序结果:FDCAB
入度最小的先遍历
(1)把入度为 $0$ 的顶点(无前驱)找到并打印
(2)删除入度为 $0$ 顶点,及其所有出边
(3)循环 (1)(2) 直至图为 NULL,即全部顶点均已输出,或是不存在入度为 $0$ 的顶点,这种情况则说明有向图中存在环
采用邻接表作为有向图的存储结构,且在头结点中增加一个存放顶点入度的数组(indegree)。入度为零的顶点即为没有前驱的顶点,删除顶点及以它为尾的弧的操作,则可换以弧头顶点的入度减 $1$ 来实现,为避免重复检测入度为零的顶点,可另设一栈暂存所有入度为零的顶点
下面是 C 语言描述的拓扑排序的 Kahn 算法
1 | Status TopologicalSort(ALGraph G) { |
对有 $n$ 个顶点和 $e$ 条弧的有向图,进行时间复杂度的分析:
求各顶点入度 $O(e)$ ,建零入度顶点栈 $O(n)$ ,
若无环,则每一个顶点进一次栈,出一次栈,入度减 $1$ 的操作在 while 语句中总共执行 $e$ 次
所以总的时间复杂度为 $O(n + e)$
C++ 伪代码
1 | bool toposort() { |
下面的例子来自《算法导论》
下图描述的是 Bumstead 教授每天早上起床穿衣所发生的事件的次序图,教授必须先穿某些衣服,才能再穿其他衣服,比如先穿袜子后才能再穿鞋,有些服饰则可以以任意顺序穿上,比如袜子和裤子之间可以以任意次序进行穿戴,在这个有向无环图中,有向边 $(u, v)$ 表明服装 u 必须在服装 v 之前穿上,对该图进行拓扑排序所获得的就是一种合理穿衣的次序
有向无环图的顶点 | 手表 | 袜子 | 内裤 | 裤子 | 鞋 | 腰带 | 衬衣 | 领带 | 夹克 | ans(排序结果变化) |
---|---|---|---|---|---|---|---|---|---|---|
对各顶点求入度 | 0 | 0 | 0 | 1 | 3 | 2 | 0 | 1 | 2 | |
建零入度顶点栈 S 入度为 0 者进栈 |
进栈 | 进栈 | 进栈 | 进栈 | ||||||
栈顶元素出栈 | 出栈 | 衬衣 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 0 | 1 | 3 | 1 | 入度为0 进栈 |
2 | ||
栈顶元素出栈 | 出栈 | 衬衣 领带 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 0 | 1 | 3 | 1 | 1 | |||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 入度为0 进栈 |
2 | 1 | 1 | ||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 1 | 入度为0 进栈 |
1 | |||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 腰带 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 1 | 入度为0 进栈 |
||||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 腰带 夹克 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 0 | 1 | |||||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 腰带 夹克 袜子 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | 入度为0 进栈 |
||||||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 腰带 夹克 袜子 鞋 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
0 | |||||||||
栈顶元素出栈 | 出栈 | 衬衣 领带 内裤 裤子 腰带 夹克 袜子 鞋 手表 | ||||||||
邻接点入度减1 入度为 0 者进栈 |
通过上述的排序过程得到了 Bumstead 教授对自己每天早上的穿衣进行的拓扑排序
当有向图中无环时,也可利用深度优先遍历进行拓扑排序,因为图中无环,则由图中某点出发进行深度优先搜索遍历时,最先退出 DFS 函数的顶点即出度为零的顶点,是拓扑有序序列中的最后一个顶点。由此按退出 DFS 函数的先后记录下来的顶点序列即为逆向的拓扑有序序列
———《数据结构》
OI Wiki 中的代码
1 | vector<int> G[MAXN]; // vector 实现的邻接表 |
《入门经典》中的代码
1 | int c[maxn]; |