数据结构图之六(关键路径)

【1】关键路径

在我的经验意识深处,“关键”二字一般都是指临界点。

凡事万物都遵循一个度的问题,那么存在度就会自然有临界点。

关键路径也正是研究这个临界点的问题。

在学习关键路径前,先了解一个AOV网和AOE网的概念:

用顶点表示活动,用弧表示活动间的优先关系的有向图:

称为顶点表示活动的网(Activity On Vertex Network),简称为AOV网。

与AOV网对应的是AOE(Activity On Edge)网即边表示活动的网。

AOE网是一个带权的有向无环图。

网中只有一个入度为零的点(称为源点)和一个出度为零的点(称为汇点)。

其中,顶点表示事件(Event),弧表示活动,权表示活动持续的时间。

通常,AOE网可用来估算工程的完成时间。

假如汽车生产工厂要制造一辆汽车,制造过程的大概事件和活动时间如上图AOE网:

我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。

那么,显然对上图AOE网而言,所谓关键路径:

开始-->发动机完成-->部件集中到位-->组装完成。路径长度为5.5。

如果我们试图缩短整个工期,去改进轮子的生产效率,哪怕改动0.1也是无益的。

只有缩短关键路径上的关键活动时间才可以减少整个工期的长度。

例如如果制造发动机缩短为2.5天,整车组装缩短为1.5天,那么关键路径为4.5。

工期也就整整缩短了一天时间。

好吧! 那么研究这个关键路径意义何在?

假定上图AOE网中弧的权值单位为小时,而且我们已经知道黑深色的那一条为关键路径。

假定现在上午一点,对于外壳完成事件而言,为了不影响工期:

外壳完成活动最早也就是一点开始动工,最晚在两点必须要开始动工。

最大权值3表示所有活动必须在三小时之后完成,而外壳完成只需要2个小时。

所以,这个中间的空闲时间有一个小时,为了不影响整个工期,它必须最迟两点动工。

那么才可以保证3点时与发动机完成活动同时竣工,为后续的活动做好准备。

对AOE网有待研究的问题是:

(1)完成整个工程至少需要多少时间?

(2)那些活动是影响工程进度的关键?

今天研究是实例如下图所示:

假想是一个有11项活动的AOE网,其中有9个事件(V1,V2,V3...V9)。

每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。

如V1表示整个工程开始,V9表示整个共结束,V5表示a4和a5已经完成,a7和a8可以开始。

【2】关键路径算法

为了更好的理解算法,我们先需要定义如下几个参数:

(1)事件的最早发生时间etv(earliest time of vertex): 即顶点Vk的最早发生时间。

(2)事件的最晚发生时间ltv(latest time of vertex): 即顶点Vk的最晚发生时间。

  也就是每个顶点对应的事件最晚需要开始的时间,超出此时间将会延误整个工期。

(3)活动的最早开工时间ete(earliest time of edge): 即弧ak的最早发生时间。

(4)活动的最晚开工时间lte(latest time of edge): 即弧ak的最晚发生时间,也就是不推迟工期的最晚开工时间。

然后根据最早开工时间ete[k]和最晚开工时间lte[k]相等判断ak是否是关键路径。

将AOE网转化为邻接表结构如下图所示:


与拓扑序列邻接表结构不同的地方在于,弧链表增加了weight域,用来存储弧的权值。

求事件的最早发生时间etv的过程,就是从头至尾找拓扑序列的过程。

因此,在求关键路径之前,先要调用一次拓扑序列算法的代码来计算etv和拓扑序列表。

数组etv存储事件最早发生时间

数组ltv存储事件最迟发生时间

全局栈用来保存拓扑序列

注意代码中的粗部分与原拓扑序列的算法区别。

第11-15行 初始化全局变量etv数组。

第21行 就是讲要输出的拓扑序列压入全局栈。

第 27-28 行很关键,它是求etv数组的每一个元素的值。

比如:假如我们已经求得顶点V0的对应etv[0]=0;顶点V1对应etv[1]=3;顶点V2对应etv[2]=4

现在我们需要求顶点V3对应的etv[3],其实就是求etv[1]+len<V1,V3>与etv[2]+len<V2,V3>的较大值

显然3+5<4+8,得到etv[3]=12,在代码中e->weight就是当前弧的长度。如图所示:

由此也可以得到计算顶点Vk即求etv[k]的最早发生时间公式如上。

下面具体分析关键路径算法:

1.  程序开始执行。第5行,声明了etv和lte两个活动最早最晚发生时间变量

2.  第6行,调用求拓扑序列的函数。

  执行完毕后,全局数组etv和栈的值如下所示796,也就是说已经确定每个事件的最早发生时间。

3.  第7-9行初始化数组ltv,因为etv[9]=27,所以数组当前每项均为27。

4.  第10-19行为计算ltv的循环。第12行,先将全局栈的栈头出栈,由后进先出得到gettop=9。

  但是,根据邻接表中信息,V9没有弧。所以至此退出循环。

5.  再次来到第12行,gettop=8,在第13-18行的循环中,V8的弧表只有一条<V8,V9>

  第15行得到k=9,因为ltv[9]-3<ltv[8],所以ltv[8]=ltv[9]-3=24,过程如下图所示:

6.  再次循环,当gettop=7,5,6时,同理可计算出ltv相对应的值为19,25,13。

  此时ltv值为:{27,27,27,27,27,13,25,19,24,27}

7.  当gettop=4时,由邻接表信息可得到V4有两条弧<V4,V6>和<V4,V7>。

  通过第13-18行的循环,可以得到ltv[4]=min(ltv[7]-4,ltv[6]-9)=min(19-4,25-9)=15

  过程分析如下图所示:

  当程序执行到第20行时,相关变量的值如下图所示。

  比如etv[1]=3而ltv[1]=7表示(如果单位按天计的话):

  哪怕V1这个事件在第7天才开始也是可以保证整个工程按期完成。

  你也可以提前V1时间开始,但是最早也只能在第3天开始。

8.  第20-31行是求另两个变量活动最早开始时间ete和活动最晚时间lte。

  当 j=0 时,从V0顶点开始,有<V0,V2>和<V0,V1>两条弧。

  当 k=2 时,ete=etv[j]=etv[0]=0

  lte=ltv[k]-e->weight=ltv[2]-len<v0,v2>=4-4=0  此时ete == lte

  表示弧<v0,v2>是关键活动,因此打印。

  当 k=1 时,ete=etv[j]=etv[0]=0

  lte=ltv[k]-e->weight=ltv[2]-len<v0,v1>=7-3=4  此时ete != lte

  表示弧<v0,v1>并不是关键活动。如图所示:

说明:ete表示活动<Vk,Vj>的最早开工时间,是针对弧来说的。

但是只有此弧的弧尾顶点Vk的事件发生了,它才可以开始,ete=etv[k]。

lte表示的是活动<Vk,Vj>最晚开工时间,但此活动再晚也不能等V1事件发生才开始。

而必须要在V1事件之前发生,所以lte=ltv[j]-len<Vk,Vj>。

9.  j=1 直到 j=9 为止,做法完全相同。

最终关键路径如下图所示:

注意:本例是唯一一条关键路径,并不等于不存在多条关键路径。

如果是多条关键路径,则单是提高一条关键路径上的关键活动速度并不是能导致整个工程缩短工期、

而必须提高同时在几条关键路径上的活动的速度。


【3】关键路径是代码实现

  源码下载地址

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Terry_dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值