(关键路径)
导读
大家好,很高兴又和大家见面啦!!!
在之前的图论学习中,我们已经掌握了拓扑排序这一重要概念。
拓扑排序是针对有向无环图(DAG)的一种线性序列,它要求对于图中的任意一条有向边(u,v),在序列中u都出现在v之前。
这种排序方法在任务调度、依赖关系分析等场景中有着广泛的应用。
与拓扑排序相对应的是逆拓扑排序:
它要求对于任意一条有向边(u,v),在序列中v都出现在u之前。
这两种排序方法虽然方向相反,但都体现了有向无环图中活动之间的前后依赖关系。
今天,我们将在这两种排序方法的基础上,深入探讨图论中的一个核心应用——关键路径分析。
关键路径方法源于项目管理领域,用于确定项目中哪些任务的时间延迟会直接影响整个项目的完成时间。
- 通过拓扑排序,我们可以确定项目中各个事件的合理发生顺序;
- 通过逆拓扑排序,我们则可以分析在不影响总工期的前提下,各个事件最迟应该何时发生。
这两种分析方法的结合,正是求解关键路径的核心思路。
在本文中,我们将首先介绍 AOE网(Activity On Edge network)这一重要概念,然后详细讲解如何通过拓扑排序和逆拓扑排序来求解关键路径,并通过具体实例演示整个计算过程。
掌握关键路径分析方法,不仅有助于理解项目管理中的时间优化问题,也为后续学习更多高级图算法奠定坚实基础。
让我们开始探索如何找出那些"牵一发而动全身"的关键活动吧!
一、重要概念
1.1 AOE网
在带权有向图中,以顶点表示事件,以有向边表示活动,以边上的权值表示完成该活动的开销(如完成活动所需的时间),称之为用边表示活动的网络,简称AOE网。
与AOV网一样,AOE网同样为有向无环图(DAG图),不过不同的是,二者的边和顶点所代表的含义是不同的:
- AOV网:顶点表示活动,有向边表示活动的先后顺序,有向边并无权值
- AOE网:顶点表示事件,有向边表示活动,边权值表示完成该活动的开销
AOE网具有一下两个性质:
- 只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始;
- 只有在进入某顶点的各有向边所代表的活动都已结束时,该顶点所代表的事件才能发生
graph LR
a[准备买苹果]--->|买苹果5min|b[到家洗苹果]--->|洗苹果3min|c[吃苹果]
b--->|洗苹果3min|d[开始削皮]--->|削苹果皮5min|c
这里可能有点绕,这里我们通过吃苹果这个工程来进行理解:
-
只有在准备买苹果这个事件发生后,买苹果这个活动才能开始;
-
对于吃苹果这个顶点的所代表的事件,只有在指向该顶点的所有有向边所代表的活动(洗苹果3min与削苹果皮5min)都已经结束时,该事件才能发生;
在AOE网中,仅有一个入度为0的顶点,称为开始顶点(源点),它表示整个工程的开始;也仅有一个出度为0的顶点,称为结束顶点(汇点),它表示整个工程的结束。
1.2 关键路径
在AOE网中,有些活动是可以并行进行的。这里我们将吃苹果这个工程给稍作修改:
graph LR
a[开始]--->|买苹果3min|b[准备洗苹果]--->|洗苹果3min|c[准备削苹果皮]
a--->|买水果刀5min|c[准备削苹果皮]--->|削苹果皮3min|d[吃苹果]
在上图中,我们以开始这一顶点作为源点,当开始这一事件发生后,买苹果和买水果刀这两个活动是可以并行进行的,就比如,我们可以自己去买苹果,让家里的其他人去买水果刀,因此这两个活动可以并行进行。
从源点到汇点的有向路径可能有多条,并且这些路径长度可能不同。就比如上图中就存在两条路径:
- 路径1
graph LR
a[开始]--->|买苹果3min|b[准备洗苹果]--->|洗苹果3min|c[准备削苹果皮]--->|削苹果皮3min|d[吃苹果]
- 路径2
graph LR
a[开始]
a--->|买水果刀5min|c[准备削苹果皮]--->|削苹果皮3min|d[吃苹果]
完成不同路径上的活动所需的时间虽然不同,但是只有所有路径上的活动都已完成,整个工程才能算结束。因此,从源点到汇点的所有路径中,具有最大路径长度的路径称为关键路径,而把关键路径上的活动称为关键活动。
就如上述两条路径的长度分别为:
- 路径1:9min
- 路径2:8min
对于这两条路径而言,路径1的长度明显大于路径2的长度,因此路径1就是该工程的关键路径,该路径上的活动(买苹果、洗苹果、削苹果皮)就是关键活动。
完成整个工程的最短时间就是关键路径的长度,即关键路径上个活动花费开销的总和。也就是说,完成吃苹果这个工程的最短时间需要花费9min。
这是因为关键活动影响了整个工程的时间,即若关键活动不能按时完成,则整个工程的完成时间就会延长。如下所示:
graph LR
a[开始]--->|买苹果3min->5min|b[准备洗苹果]--->|洗苹果3min|c[准备削苹果皮]--->|削苹果皮3min|d[吃苹果]
当原本仅需3min买苹果的时间没能按时完成,而是延长到了5min,那么整个工程的完成的最短时间就由原先的9min延长到了11min。
因此,只要找到了关键活动,就找到了关键路径,也就可以得出最短完成时间。
在关键活动中,有以下参量我们需要了解:
- 事件 $v_k$ 的最早发生时间 $v_e(k)$:指从源点 $v_1$ 到顶点 $v_k$ 的最长路径长度。
- 事件 $v_k$ 的最早发生时间决定了所有从 $v_k$ 开始的活动能够开工的最早时间。可用下面的递推公式来计算:
$v_e$(源点) = 0 $v_e(k) = Max{v_e(j) + Weight(v_j, v_k)}$ ,$v_k$ 为 $v_j$ 的任意后继,$Weight(v_j, v_k)$ 表示 $<v_j, v_k>$ 上的权值
- 事件 $v_k$ 的最迟发生时间 $v_l(k)$:指在不推迟整个工程完成的前提下,即保证它的后继事件 $v_j$ 在其最迟发生时间 $v_l(j)$ 能够发生时,该事件最迟必须发生的时间。
$v_l$(汇点) = $v_e$(汇点) $v_l(k) = Min{v_l(j) - Weight(v_k, v_j)}, v_k$ 为 $v_j$ 的任意前驱
-
活动 $a_i$ 的最早开始时间 $e(i)$:指该活动弧的起点所代表的事件的最早发生时间。
- 若边 $<v_k, v_j>$ 表示活动 $a_i$ ,则有 $e(i) = v_e(k)$
-
活动 $a_i$ 的最迟开始时间 $l(i)$:指该活动弧的终点所表示事件的最迟发生时间与该活动所需时间之差。
- 若边 $<v_k, v_j>$ 表示活动 $a_i$ ,则有 $l(i) = v_l(j) - Weight(v_k, v_j)$
-
一个活动 $a_i$ 的最迟开始时间 $l(i)$ 和其最早开始时间 $e(i)$ 的差额 $d(i) = l(i) - e(i)$:指该活动完成的时间余量,即在不增加完成整个工程所需总时间的情况下,活动 $a_i$ 可以拖延的时间。
- 若一个活动的时间余量为零,则说明该活动必须要如期完成,否则就会拖延整个工程的进度,所以称 $l(i) - e(i) = 0$ 即 $l(i) = e(i)$ 的活动 $a_i$ 是关键活动
二、关键路径算法
2.1 算法步骤
求关键路径的算法步骤如下所示:
- 从源点出发,令 $v_e$(源点) = 0 ,按拓扑有序求其余顶点的最早发生时间 $v_e()$
- 从汇点出发,令 $v_l$(汇点) = $v_e$(汇点),按逆拓扑有序求其余顶点的最迟发生时间 $v_l()$
- 根据各顶点的 $v_e()$ 值求所有弧的最早开始时间 $e()$
- 根据各顶点的 $v_l()$ 值求所有弧的最迟开始时间 $l()$
- 求 $AOE$ 网中所有活动的差额 $d()$ ,找出所有 $d() = 0$ 的活动构成关键路径
2.2 实例演示
接下来我们根据下面的实例来理解算法的各个步骤:
graph LR
a[v1] --->|a1 = 3|b[v2]--->|a4 = 3|c[v5]--->|a8 = 1|d[v6]
b--->|a3 = 2|f[v4]--->|a7 = 2|d
e--->|a5 = 4|f
a--->|a2 = 2|e[v3]--->|a6 = 3|d
第一步:从源点出发,令 $v_e$(源点) = 0 ,按拓扑有序求其余顶点的最早发生时间 $v_e()$
按照拓扑排序的规则,我们可以得到其中一个拓扑排序序列为:
- $v_1, v_2, v_3, v_4, v_5, v_6$
按照该顺序,我们依次对个顶点的最早发生时间进行求解:
-
$v_{e_1} = 0$:对于源点,该事件的最早发生时间肯定事从零时刻开始,因此其最早发生时间为: $v_{e_1} = 0$;
-
$v_{e_2} = Max{v_{e_1} + a_1}$:对于顶点2,该事件要想发生,就一定得等活动 $a_1$ 结束后,而 $a_1 = 3$ ,因此,该顶点事件的最早发生时间为: $v_{e_2} = Max{0 + 3} = 3$;
-
$v_{e_3} = Max{v_{e_1} + a_2}$:对于顶点3,该事件要想发生,就一定得等活动 $a_2$ 结束后,而 $a_2 = 2$ ,因此,该顶点事件的最早发生时间为: $v_{e_3} = Max{0 + 2} = 2$;
-
$v_{e_4} = Max{v_{e_2} + a_3, v_{e_3} + a_5}$:对于顶点4,该事件要想发生,就一定得等活动 $a_3$ 与活动 $a_5$ 均结束后才能发生,而 $a_3 = 2, a_5 = 4$ ,因此,该顶点事件的最早发生时间为:$v_{e_4} = Max{3 + 2, 2 + 4} = Max{5, 6} = 6$;
-
$v_{e_5} = Max{v_{e_2} + a_4}$:对于顶点5,该事件要想发生,就一定得等活动 $a_4$ 结束后,而 $a_4 = 3$ ,因此,该顶点事件的最早发生时间为:$v_{e_5} = Max{3 + 3} = 6$
-
$v_{e_6} = Max{v_{e_5} + a_8, v_{e_4} + a_7, v_{e_3} + a_6}$:对于顶点6,该事件要想发生,就一定得等活动 $a_6, a_7, a_8$ 均结束后才能发生,而 $a_6 = 3, a_7 = 2, a_8 = 1$ ,因此,该顶点事件的最早发生时间为:$v_{e_6} = Max{6 + 1, 6 + 2, 2 + 3} = Max{7, 8, 5} = 8$
第二步:从汇点出发,令 $v_l$(汇点) = $v_e$(汇点),按逆拓扑有序求其余顶点的最迟发生时间 $v_l()$
按照逆拓扑排序规则,我们可以得到其中一个逆拓扑排序序列为:
- $v_6, v_5, v_4, v_3, v_2, v_1$
按照该顺序,我们依次对各个顶点的最迟发生时间进行求解:
-
$v_{l_6} = v_{e_6}$:对于汇点,我们假设该事件的最迟发生时间与最早发生时间一致,那么其对应的最迟发生时间则为:$v_6 = 8$
-
$v_{l_5} = Min{v_{l_6} - a_8}$:对于顶点5,在他开始后,要想让顶点6的事件准时开始,那么他就一定要给活动 $a_8$ 留出足够的时间,而 $a_8 = 1$ ,因此,顶点5的最迟发生时间为:$v_{l_5} = Min{8 - 1} = 7$
-
$v_{l_4} = Min{v_{l_6} - a_7}$:对于顶点4,在他开始后,要想让顶点6的事件准时开始,那么他就一定要给活动 $a_7$ 留出足够的时间,而 $a_7 = 2$ ,因此,顶点4的最迟发生时间为:$v_{l_4} = Min{8 - 2} = 6$
-
$v_{l_3} = Min{v_{l_6} - a_6, v_{l_4} - a_5}$:对于顶点3,在他开始后,要想让顶点6与顶点4的事件准时开始,那么他就一定要给活动 $a_6,a_5$ 留出足够的时间,而 $a_6 = 3, a_5 = 4$ ,因此,顶点3的最迟发生时间为:$v_{l_3} = Min{8 - 3, 6 - 4} = 2$
-
$v_{l_2} = Min{v_{l_5} - a_4, v_{l_4} - a_2}$:对于顶点2,在他开始后,要想让顶点5与顶点4的事件准时开始,那么他就一定要给活动 $a_4,a_3$ 留出足够的时间,而 $a_4 = 3, a_3 = 2$ ,因此,顶点2的最迟发生时间为:$v_{l_2} = Min{7 - 3, 6 - 2} = 4$
-
$v_{l_1} = Min{v_{l_2} - a_1, v_{l_3} - a_2}$:对于顶点1,在他开始后,要想让顶点2与顶点3的事件准时开始,那么他就一定要给活动 $a_1,a_2$ 留出足够的时间,而 $a_1 = 3, a_2 = 2$ ,因此,顶点1的最迟发生时间为:$v_{l_3} = Min{4 - 3, 2 - 2} = 0$
现在我们就得到了各个顶点的最早开始时间与最迟开始时间,如下所示:
v~1~ | v~2~ | v~3~ | v~4~ | v~5~ | v~6~ | |
---|---|---|---|---|---|---|
最早开始时间v~e~ | 3 | 2 | 6 | 6 | 8 | |
最迟开始时间v~l~ | 4 | 2 | 6 | 7 | 8 |
|
第三步:根据各顶点的 $v_e()$ 值求所有弧的最早开始时间 $e()$
按照前面我们求出的各顶点的最早开始时间,我们就可以求得各弧的最早开始时间:
-
弧 $<v_1, v_2>$:其表示的活动为 $a_1$ ,因此其最早开始时间为:$e_1 = v_{e_1} = 0$
-
弧 $<v_1, v_3>$:其表示的活动为 $a_2$ ,因此其最早开始时间为:$e_2 = v_{e_1} = 0$
-
弧 $<v_2, v_4>$:其表示的活动为 $a_3$ ,因此其最早开始时间为:$e_3 = v_{e_2} = 3$
-
弧 $<v_2, v_5>$:其表示的活动为 $a_4$ ,因此其最早开始时间为:$e_4 = v_{e_2} = 3$
-
弧 $<v_3, v_4>$:其表示的活动为 $a_5$ ,因此其最早开始时间为:$e_5 = v_{e_3} = 2$
-
弧 $<v_3, v_6>$:其表示的活动为 $a_6$ ,因此其最早开始时间为:$e_6 = v_{e_3} = 2$
-
弧 $<v_4, v_6>$:其表示的活动为 $a_7$ ,因此其最早开始时间为:$e_7 = v_{e_4} = 6$
-
弧 $<v_5, v_6>$:其表示的活动为 $a_8$ ,因此其最早开始时间为:$e_8 = v_{e_5} = 6$
第四步:根据各顶点的 $v_l()$ 值求所有弧的最迟开始时间 $l()$
同理,我们也可以求得各弧的最迟开始时间:
-
弧 $<v_1, v_2>$:其表示的活动为 $a_1$ ,因此其最迟开始时间为:$l_1 = v_{l_2} - a_1= 4 - 3 = 1$
-
弧 $<v_1, v_3>$:其表示的活动为 $a_2$ ,因此其最迟开始时间为:$l_2 = v_{l_3} - a_2 = 2 - 2 = 0$
-
弧 $<v_2, v_4>$:其表示的活动为 $a_3$ ,因此其最迟开始时间为:$l_3 = v_{l_4} - a_3= 6 - 2 = 4$
-
弧 $<v_2, v_5>$:其表示的活动为 $a_4$ ,因此其最迟开始时间为:$l_4 = v_{l_5} - a_4 = 7 - 3 = 4$
-
弧 $<v_3, v_4>$:其表示的活动为 $a_5$ ,因此其最迟开始时间为:$l_5 = v_{l_4} - a_5 = 6 - 4 = 2$
-
弧 $<v_3, v_6>$:其表示的活动为 $a_6$ ,因此其最迟开始时间为:$l_6 = v_{l_6} - a_6 = 8 - 3 = 5$
-
弧 $<v_4, v_6>$:其表示的活动为 $a_7$ ,因此其最迟开始时间为:$l_7 = v_{l_6} - a_7 = 8 - 2 = 6$
-
弧 $<v_5, v_6>$:其表示的活动为 $a_8$ ,因此其最迟开始时间为:$l_8 = v_{l_6} - a_8 = 8 - 1 = 7$
现在我们就得到了所有弧的最早开始时间与最迟开始时间,如下所示:
a~1~ | a~2~ | a~3~ | a~4~ | a~5~ | a~6~ | a~7~ | a~8~ | |
---|---|---|---|---|---|---|---|---|
最早开始时间e | 3 | 3 | 2 | 2 | 6 | 6 | ||
最迟开始时间l | 1 | 4 | 4 | 2 | 5 | 6 | 7 |
第五步:求 $AOE$ 网中所有活动的差额 $d()$ ,找出所有 $d() = 0$ 的活动构成关键路径
根据求得的各弧的最早开始时间与最迟开始时间,我们便可以求得各活动的二者时间差额,如下所示:
a~1~ | a~2~ | a~3~ | a~4~ | a~5~ | a~6~ | a~7~ | a~8~ | |
---|---|---|---|---|---|---|---|---|
最迟开始时间l | 1 | 4 | 4 | 2 | 5 | 6 | 7 | |
最早开始时间e | 3 | 3 | 2 | 2 | 6 | 6 | ||
时间差额d | 1 | 1 | 1 | 3 | 1 |
根据差额我们可以找到关键活动:$a_2, a_5, a_7$,而这些活动所对应的关键路径为:
graph LR
a[v1]--->|a2 = 2|e[v3]--->|a5 = 4|f[v4]--->|a7 = 2|d[v6]
2.3 关键活动与关键路径的特性
在一项工程中,关键路径与关键活动具有一下特性:
- 若关键活动的时间增加,则整个工程的工期将会延长
- 若关键活动的时间在一定范围内缩短,则整个工程的工期会被缩短
- 若关键活动的时间缩短超过这一范围,则该关键路径会转为非关键路径,其对应的活动也会变为非关键活动。
- 若 AOE网 中的关键路径并不唯一,当网中存在多条关键路径时,只提高一条关键路径上的关键活动速度,并不能缩短整个工程的工期;只有加快那些包括所有关键路径上的关键活动,才能达到缩减工期的目的
下面我们还是以吃苹果这个工程来理解这些特性:
graph LR
a[v1出门]--->|买苹果a1 = 3|b[v2准备洗苹果]--->|洗苹果a3 = 3|c[v3准备削皮]
a--->|买水果刀a2 = 6|c--->|削苹果皮a4 = 5|d[v4吃苹果]
在这个工程中,两条路径:
- 路径1
graph LR
a[v1出门]--->|买苹果a1 = 3|b[v2准备洗苹果]--->|洗苹果a3 = 3|c[v3准备削皮]--->|削苹果皮a4 = 5|d[v4吃苹果]
- 路径2
graph LR
a[v1出门]--->|买水果刀a2 = 6|c[v3准备削皮]--->|削苹果皮a4 = 5|d[v4吃苹果]
均为该工程的关键路径,其中活动 $a_4$ 为两条路径共有的关键活动,那么当我们缩短该工程的时间时,整个工程的工期都会缩短;
当我们缩短活动 $a_1$ 或者活动 $a_3$ 的时间时,此时路径1不再是关键路径,其路径上的活动也不再是关键活动,因此,其时间变化并不会影响整个工程的工期;
当我们增加某个活动的时间是,整个工程的工期将被延长,且不包含该活动的路径变为非关键路径;
graph LR
a[v1出门]--->|买苹果a1 = 5|b[v2准备洗苹果]--->|洗苹果a3 = 3|c[v3准备削皮]
a--->|买水果刀a2 = 6|c--->|削苹果皮a4 = 5|d[v4吃苹果]
在上图中,关键路径为:
graph LR
a[v1出门]--->|买苹果a1 = 5|b[v2准备洗苹果]--->|洗苹果a3 = 3|c[v3准备削皮]--->|削苹果皮a4 = 5|d[v4吃苹果]
工程的工期为:$5 + 3 + 5 = 13$
- 当我们缩短关键路径上的活动 $a_1, a_3$ 的总时间时间在 $0 \sim 2$ 这个范围内时,整个工程的工期将被缩短
- 当超过这个范围时,该路径将变为非关键路径,路径上的活动 $a_1, a_3$ 将变为非关键活动
结语
今天的内容到这里就全部结束了,通过今天的学习,我们系统掌握了图论中极具实用价值的关键路径分析方法。让我们来详细回顾本文的核心知识点:
-
AOE网的核心概念
-
顶点表示事件,有向边表示活动,边权值表示活动开销
-
与AOV网的区别:AOV网顶点表示活动,AOE网边表示活动
-
两个重要性质:事件触发活动的开始,活动完成触发事件的发生
-
仅有一个源点(入度为0)和一个汇点(出度为0)
-
-
关键路径的核心定义
-
从源点到汇点的最长路径决定了整个工程的最短工期
-
关键路径上的活动称为关键活动,其延迟会直接影响总工期
-
工程最短完成时间 = 关键路径的长度
-
-
五大关键参数体系
-
事件最早发生时间ve(k):ve(k) = Max{ve(j) + Weight(vj, vk)}
-
事件最迟发生时间vl(k):vl(k) = Min{vl(j) - Weight(vk, vj)}
-
活动最早开始时间e(i):e(i) = ve(k)(活动起点的最早发生时间)
-
活动最迟开始时间l(i):l(i) = vl(j) - Weight(vk, vj)
-
时间余量d(i):d(i) = l(i) - e(i),d(i)=0的活动为关键活动
-
-
关键路径五步算法:
- 拓扑排序求所有事件的最早发生时间ve()
- 逆拓扑排序求所有事件的最迟发生时间vl()
- 根据ve()求所有活动的最早开始时间e()
- 根据vl()求所有活动的最迟开始时间l()
- 计算时间余量d(),d(i)=0的活动构成关键路径
-
关键路径的重要特性
-
关键活动时间增加 → 总工期延长
-
关键活动适度缩短 → 总工期缩短
-
关键活动过度缩短 → 可能转为非关键活动
-
多关键路径时需同时优化所有路径上的关键活动
-
💡 学习建议 建议大家亲手重现文中的实例计算,重点掌握五步算法的执行逻辑。关键路径分析在项目管理、工程优化等领域应用广泛,是图论通向实际应用的重要桥梁。
✨ 点击关注,系统学习图论完整知识体系!
👍 点赞支持 - 让更多需要的朋友看到这篇干货
⭐ 收藏备忘 - 建立个人知识库随时查阅
📤 转发分享 - 帮助更多正在学习图论的同学
💬 评论交流 - 有任何问题欢迎随时讨论
记得关注我,接下来我们将深入讲解图的更多实际应用场景!感谢阅读,我们下期再见!