前言
对于一个工程项目,在管理的时候可以将其按规模、功能等特征划分成不同的模块,而当各个小模块完成并进行整合后整个项目也就相应完成。在划分的方法中,主流的方法是按事件、活动的方式进行划分,并且为了直观表示,可以对应到一张有向图中(一般是DAG图)。
对于管理者总是关心各个模块的完成时间节点以及整体模块的完成时间时间节点,这些节点我们对于到图中的点 Vertex ,也成为事件,表示这个时间节点 A 事件完成;对于模块完成所要付出的前置工作我们将其对于到图中的边 Edge,也叫做活动,表示完成事件 A 的前置任务/活动。
我们直观地来考虑图的含义, 例如:从A 点到 B 点有一条有向边 e 。对应可以描述为:活动 e 需要在 A 事件完成后才能做,或者完成事件 B 至少需要先把活动 e 完成。当然,边权表示的是改活动的完成时间。
对于上述的图,我们称之为 AOE-网(Activity on Edge Network),因为主要的工作被拆分到各个边上,区别于拓扑排序的 AOV 网的是,本定义更关注于边,也就是活动的大小。
对于求关键路径的算法我们可以求出工程的最短完成时间。
定义
对于一个事件,要在它前置活动,也就是准备工作都完成后才能开始,也就是判定一个节点开始的标志就是看他所有入度的边是否都完成。
关键路径,从图中源点到汇点的最长路径,也可以理解为影响整个项目周期的主分支。为了求这个路径,我们定义了以下四个参数:
- ve(i),表示事件/节点 i 的最早开始时间
- vl(i),表示事件/节点 i 的最迟开始时间
- e(j),表示活动/边 j 的最早开始时间
- l(j),表示活动/边 j 的最迟开始时间
ve(i)
节点 i 的最早开始时间取决于其前置节点最早的开始时间加上两个事件过渡的活动时间的最大值。
v
e
[
i
]
=
m
a
x
(
v
e
[
i
]
,
v
e
[
k
]
+
p
a
t
h
[
k
]
[
i
]
)
ve[i]=max(ve[i],ve[k]+path[k][i])
ve[i]=max(ve[i],ve[k]+path[k][i])
为什么取最大呢?因为这才能保证前置活动都完成,在每个节点都没有延迟的时候,前置事件的最早开始时间和前置活动最早开始时间相同。
当然,如果前置节点点有延迟时间的话还要加上这个延迟时间,不过一般不考虑,也就是这个延迟时间我们默认视它为 0 。
注:求该值的顺序要按拓扑排序的顺序进行,也就是从前往后的。
vl(i)
节点 i 的最迟开始时间取决于其后置节点最迟的开始时间扣除两个事件过渡的活动时间的最小值。
v
l
[
i
]
=
m
i
n
(
v
l
[
i
]
,
v
l
[
k
]
−
p
a
t
h
[
i
]
[
k
]
)
vl[i]=min(vl[i], vl[k]-path[i][k])
vl[i]=min(vl[i],vl[k]−path[i][k])
在初学的时候这个地方会有两种认识偏差:
-
不应该是后置节点的最早开始时间扣除中间的权值嘛?也就是当前点的最迟开始时间不能影响后置节点的最早开始时间
答:实际上这是一个在图上 DP 的问题,主要可能产生后效性、最优性的错误,不过这里暂时不从这方面讲。通俗的理解就是:
第一,一个事件的最早开始时间一定是不受前置节点的最晚开始时间影响的,不然我们不就能够找到一个更早的开始时间了吗;
第二,求解的时候我们应该一个边界对应一个边界,只要求点不影响关键路径,甚至可能出现当前节点的最迟开始时间比后置节点的最早开始时间要大,但前提是不影响整个工程的时间。 -
为什么取最小值?最迟不应该取最大值吗?
答:一个事件可能有多个后置事件,如果当前事件的最迟开始时间是由后继事件的最迟开始时间的最大值传递过来的,相当于从后置事件的最右边界传递过来,那么,可能会导致一些更早的后置事件的最迟开始事件后移,这样就违背了我们求好的后置节点的最迟开始时间的结果了。
当然,如果当前点有延迟时间的话还要减去这个延迟时间,不过一般不考虑,也就是这个延迟时间我们默认视它为 0 。
注:求该值的顺序要按逆拓扑排序的顺序进行,也就是从后往前的。
e(j)
边 j 的最早开始时间和它的起点的事件最早开始时间相同。
e
(
j
)
=
v
e
(
i
)
,
i
为
j
边
的
起
点
e(j)=ve(i),i为j边的起点
e(j)=ve(i),i为j边的起点
如果起点有延迟,还要加上这个延迟时间,不过一般不考虑。
l(j)
边 j 的最迟开始时间为它的终点的事件最迟开始时间扣除边的边权,也就是本活动的时间,当然,如果出点如果有延迟还要减去这个延迟。
l
(
j
)
=
v
l
(
i
)
−
l
e
n
(
j
)
,
i
为
j
边
的
终
点
,
l
e
n
为
边
权
l(j)=vl(i)-len(j),i为j边的终点,len为边权
l(j)=vl(i)−len(j),i为j边的终点,len为边权
时间余量
d ( j ) = l ( j ) − e ( j ) d(j)=l(j)-e(j) d(j)=l(j)−e(j) 表示每个活动的时间余量,也就是活动能拖延的时间,当且仅当这个余量为 0 时,该活动为关键活动,也就是说关键活动没有可以拖延的余地。
关键路径
当 e ( j ) = = l ( j ) e(j)==l(j) e(j)==l(j) 时,对应的边 a ( j ) a(j) a(j) 就是关键活动,多个关键活动串接起来的边集合就是关键路径。
算法
- 求拓扑排序,计算 v e ( i ) ve(i) ve(i)
- 求逆拓扑排序,计算 v l ( i ) vl(i) vl(i)
- 计算 e ( j ) 、 l ( j ) e(j)、l(j) e(j)、l(j)
求解步骤
例题
1.