[知识点] 8.4 拓扑排序与关键路径
总目录 > 8 图论 > 8.4 拓扑排序与关键路径
前言
想起当年参加 CTSC 时 DP 王 Zed 用拓扑序 DP 过了一道题直接一飞冲天,而一旁的蒟蒻拿了个 10 分 gg。
子目录列表
1、DAG 与 AOV 网
2、拓扑排序
3、AOE 网与关键路径
8.4 拓扑排序与关键路径
1、DAG 与 AOV 网
一个无环的有向图被称作有向无环图(DAG,Directed Acycline Graph)。它不同于有向树,DAG 将有向边改成无向边后可以出现环,而有向树不行。
DAG 应用广泛,除了在动态规划中提到了 4.4 树形 / DAG / 数位 DP ,它往往是描述一项工程或系统进行过程的有效工具。所有的大型工程(Project)都可以被划分为若干个子工程,这些子工程被称作活动(Activity)。在整个工程中,有些活动必须在其他相关活动完成后才能开始,也就是说,一个活动的开始是以其所有前序活动的完工为先决条件。部分活动没有先决条件,则可以随时开始。人们关心这个工程是否能够顺利进行,如果能,如何安排工程完成的先后顺序使得所花费时间最短,而 DAG 正是形象反映出整个工程的活动之间的约束关系的最佳选择。
以顶点表示活动,有向边表示活动之间的约束关系,这样的 DAG 被称作顶点活动网(AOV 网,Activity On Vertex Network)。
2、拓扑排序
① 定义
根据上述对 AOV 网的定义,我们先来举个例子。
杰克对计算机很感兴趣,进入大学后他如愿以偿地进修计科专业。学校发给他一份培养方案,上面写满了各种课程:
课程代号 课程名称 先修课程
C1 高等数学 无
C2 C 程序设计 无
C3 离散数学 C1, C2
C4 数据结构 C3, C5
C5 C++ 程序设计 C2
C6 编译技术 C4, C5
C7 操作系统 C4, C9
C8 大学物理 C1
C9 计算机组成 C8
杰克表示头都晕了,他想理清楚这些课程的关系。这时候,使用一张 AOV 网来展示:
一下子就变得清晰起来 —— 他需要先上 C1 高等数学 和 C2 C语言设计 两门课程,而后 C3 离散数学,C5 C++程序设计,C8 大学物理 就可以上了;以此类推,最后上 C7 操作系统,C6 编译技术。
AOV 网为什么是 DAG 图?如果出现了环,对于课程的选择则必然出现问题:假设 C1 是 C8 的前序课程,而 C8 又是 C1 的前序课程,那么两门课上课的先后顺序则无法确定,导致整个课程学习无法完成。这种情况如果出现在工程中,则称为死锁或死循环,是必须要避免的。
杰克整理好了他的上课顺序,为:
{C1, C2, C3, C8, C5, C4, C9, C7, C6}
根据 AOV 网求得的这样的序列,我们称之为拓扑序(Topological order)。构造拓扑序的这个过程,称之为拓扑排序(Topological sort)。
② 性质
注意到,比如刚开学时,杰克可以选择先上 C1 再上 C2,反之同样可以;学完了 C1 和 C2,杰克可以先上 C3 也可以先上 C8……也就是说,拓扑序列并不具有唯一性。
通过归纳证明,容易得到如下推理:
图能进行拓扑排序的充分必要条件是该图为 DAG。两者为等价关系。判定一个图是否为 DAG,检验它能否进行拓扑排序即可。
③ Kahn 算法
Kahn 算法用于实现拓扑排序。预处理出所有结点的入度,将入度为 0 的结点加入集合 S,每次从 S 中取出任意一个顶点 u 并保存到数组 ans,遍历以 u 为起点的所有边,并将对应终点的入度减 1,如果减到了 0,则加入集合 S,不断重复,直到所有顶点输出完毕。ans 数组最终的结果即拓扑序。
Kahn 算法使用队列实现。
④ 例题与代码
AOV 网在生活中还是很常见的,课程之间的关系,组件之间的依赖关系,工程的先后顺序等等。下面还给出一道例题:
【例题】【士兵排队】
有 n 名士兵(1 <= n <= 100),编号依次为 1, 2, 3, ..., n。进行队列训练时,指挥官要把一些士兵从高到矮依次排成一行,但现在指挥官不能直接获得每个士兵的身高信息,只能获得“P1 比 P2 高”这样的比较结果(P1, P2 ∈ {1, 2, ..., n},记为 P1 > P2),如“A > B”表示 A 比 B 高。编一程序,根据所得到的比较结果求出符合条件的排队方案。注:比较结果中没有涉及到的士兵不参加排队。
例如,设有3个士兵 1, 2, 3,给出关系 (1, 2), (2, 3)。其中 (1, 2) 表示士兵 1 高于 2,当上面的关系给出之后,可以将他们排成一队:123。
输入:第一行两个正整数 n, m,接下来 m 行中每行两个整数 x, y,表示士兵 x 比士兵 y 高。
输出:从高到矮依次输出每一个士兵的编号,如果没有答案输出 -1。
根据身高关系直接构建 AOV 网,使用 Kahn 算法求出拓扑序,即最终答案。
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define MAXN 105 5 #define MAXM 205 6 7 int n, m, u, v, a[MAXN], tot, h[MAXN], q[MAXN]; 8 9 class Edge { 10 public: 11 int v, nxt; 12 } e[MAXM]; 13 14 void add(int u, int v) { 15 tot++, e[tot] = (Edge) {v, h[u]}, h[u] = tot; 16 a[v]++; 17 } 18 19 void Kahn() { 20 for (int i = 1; i <= n; i++) 21 if (!a[i]) q[tail++] = i; 22 int head = 1, tail = 1; 23 while (head != tail) { 24 int o = q[head]; 25 for (int x = h[o]; x; x = e[x].nxt) 26 if (!--a[e[x].v]) q[tail++] = e[x].v; 27 head++; 28 } 29 } 30 31 int main() { 32 cin >> n >> m; 33 for (int i = 1; i <= m; i++) cin >> u >> v, add(u, v); 34 for (int i = 1; i <= n; i++) cout << q[i] << ' '; 35 return 0; 36 }
3、AOE 网与关键路径
① AOE 网
与 AOV 网相对应的是 AOE 网(Activity On Edge Network),即边表示活动的网。AOE 网是赋权 DAG 图,顶点表示事件(Event),弧表示活动,权表示活动持续的时间。通常,AOE 网是用来估算工程的完成时间。
与 AOV 网不同的是,AOE 网必然只有一个入度为 0 的点(起点)和一个出度为 0 的点(终点)。从起点出发,各个活动可以并行进行,所以完成工程的最短时间是从起点到终点的最长路径的长度。这条最长的路径称之为关键路径。
② 关键路径
假设开始点为 u,从 u 到结点 vi 的最长路径长度叫做事件 vi 的最早发生时间。这个时间决定了所有以事件 vi 为标志而可以开始进行的活动的最早发生时间。对于活动 ai,其最早发生时间用 ei 表示。在不推迟整个工程完成进度的前提,即不出现没有活动正在进行的情况,活动 ai 的最迟发生时间用 li 表示。li 和 ei 的差值为活动 ai 的时间余量。时间余量为 0 的活动被称作关键活动。关键路径上的所有活动均为关键活动。
也就是说,非关键活动的进度的提前与延后(在 [ei, li] 内),其实对整个工程的进度是没有影响的。所以,分析这种工程的关键在于找到关键活动,即找到 ei = li 的 ai,以提高工效,缩短工期。
③ 实现
在 Kahn 算法求出拓扑序的基础上,增加一个拓扑序结点栈,按照拓扑序搜索各个活动,及计算相应事件的最早 / 最迟开始时间。具体过程暂略。