算法与数据结构(八) AOV网的关键路径(Swift版)

上篇博客咱们介绍了AOV网的拓扑序列,请参考《数据结构(七) AOV网的拓扑排序(Swift面向对象版)》。拓扑序列中包括项目的每一个结点,沿着拓扑序列将项目进行下去是确定能够将项目完成的,可是工期不是最优的。由于拓扑序列是一个串行序列,若是按照该序列执行项目,那么就是串行执行的。咱们知道在一个项目中的一些子工程是能够并行来完成的,这也就相似咱们的多线程。今天咱们要解决的问题就是找出一个关键路径,是工期最优并保证工程的完成。什么是关键路径,咱们在下方会进行详细介绍。html

 

1、关键路径概述git

在聊关键路径以前,咱们先看一个简单的实例,以下图所示。咱们将下方这个有向无环图看作是整个工程,将每一个节点看作是该项目工程的一个子工程。子工程间又有必定的优先级。在下方图中,A的优先级最高。A作完后,B、C才能够进行开发。B、C完成后,咱们才能够开发D。从下图中咱们不难看出,该图的拓扑序列为A, B, C, D。若是咱们按照串行的方式来完成此工程的话,那么工程完成的顺序能够是A-5->B, A-8->C, B-3->D, C-10->D。总时间为26github

从上面这个序列中咱们显然能够看出来这不是最优的,由于A->B, A->C能够同时进行B->D和C->D也能够同时进行。在容许某些子工程同时进行的状况下,A->B和A-C能够同时进行,由于A->B所需时间小于A->C所需时间,因此咱们选择A->C。在A->C执行这8个小时的时间里,A->B和B->D已经执行完了,就剩下C->D了,因此关键工期为A->C->D=18算法

在求关键路径的算法中,咱们先求出每一个事件的最先完成时间。在事件最先完成的时间集合中,工程最后一步完成的时间就是咱们工程完成的最优时间。而后在工程时间最优的状况下求出每一个事件最晚完成时间。若是某个时间最先的完成时间与最晚的完成时间相同,那么该事件就是咱们的关键事件,该事件就位于咱们关键路径中。若是这样叙述有些抽象,那么咱们就拿下方这个简单图来作个类比。数组

在上方这个有向无环图中,咱们能够求出每一个事件最先发生的时间。下方截图就是每一个事件所对应的最先完成的事件,由于D是工程的尾结点,因此该工程完成的最先时间也就是D完成的最先时间,即工程完成的最先时间为18。数据结构

  

在整个工程最先完成的时间下,咱们能够从后往前推出每一个子工程最晚的完成时间。这最晚完成时间就是在不耽误整个工程最小工期的前提下,最晚的完成时间。每一个工程的最晚完成时间咱们能够倒着推出。也就是从D=18日后推出。下方就是每一个工程在保证整个工期是的完成时间是18的前提下的最晚完成时间。多线程

  

对比上了最先完成时间和最晚完成时间,咱们能够看出A, C, D这三个结点的最先完成时间与最晚完成时间相同,因此是咱们的关键结点。这几个结点链接的路径就是咱们的关键路径。因此上图中的关键路径就是A->C->D函数

 

2、关键路径算法的具体步骤post

第一部分由于示例比较简单,算是咱们本篇博客的开胃小菜,接下来进入咱们本篇博客真正的主题。在本部分,咱们仍是以原理图为主,本部分不会给出具体的代码实现,咱们只讲原理。本篇博客是在上篇博客的基础上实现的,由于每一个路径的最先执行的时间的计算依赖于拓扑序列,因此咱们依然会采用上篇博客咱们拓扑序列所使用的图的结构。下方就是咱们要求关键路径的有向无环图。若是你看过前几天博客的话,那么对下方这个图的结构应该是很是熟悉了吧,今天咱们依然会使用下方这个图来作咱们的实例。测试

  

1.最先完成时间计算

首先咱们根据拓扑排序的过程来计算出每一个结点最先完成时间。最先完成时间计算的计算过程就是在拓扑排序的过程当中添加一段记录每一个结点完成的最先时间,下方就是求最先完成时间的整个实例图,下方会给出每一步的详细介绍。下方是由拓扑排序计算最先完成时间的具体步骤,而且给出了每一步的计算规则。

其实下方这个步骤与上一篇博客中拓扑排序的步骤大同小异,只是在其基础上引入了一个数组。数组中记录的就是索引对应结点的最先完成时间,具体步骤以下所示。下方的每一步其实就是拓扑排序的步骤,只是加入了每一个结点最先完成时间的计算,由于上篇博客对拓扑排序作了详细的叙述,在此就不作过多赘述了。

  • (1)、首先咱们先建立好一个数组用于存储每一个结点最先完成的时间,数组元素的初始值为零,由于其实结点的完成时间就是0。
  • (2)、这一步中,结点A加入了拓扑序列,因此咱们能够计算出与A结点相连结点的完成时间。由于A-10->B, A的完成时间为0,0+10,因此B的此刻的完成时间为10,同理咱们能够求出F的此刻完成时间是11
  • (3)、在第三步中,F进入拓扑序列,与F结点相连的结点是G和E。因此咱们能够由F的此刻的完成时间11和F->G所需完成的时间17,求出G的此刻的完成时间11+17=28。同理咱们能够求出结点E的此刻的完成时间为11+26 = 37
  • (4)、本步骤中E结点进入拓扑序列。由上面一步咱们知道E的完成时间是37,那么不可贵出与E相连的结点D目前的完成时间是37+20 = 57。同理,H结点目前的完成时间为37+7=44
  • (5)、该步骤中B结点进入拓扑序列,与B相连的结点有B-16->G, B-12->I, B-18->C。咱们先来从B到G这条路径中G的完成时间,由B->G的完成时间为10+16=26。咱们以前求的F->G这条路径中G的完成时间是28,由于B和F都是G的前提,B和F有一个完不成,G就没法提早完成,因此咱们选择F->G这条路径来做为G的完成时间,由于FG(28)>BG(26), 低于28这个时间,G就完不成,因此此轮不更新G的完成时间。同理,此轮咱们求出I的完成时间为10+12=22C的完成时间为10+18=28
  • (6)、本步骤中C进入拓扑序列,由C-8->I可求出C路径中I的完成时间是28+8=36,与以前求得B->I路径中I的完成时间22相比要大,因此更新I的完成时间为36。由C-22->D可求出该路径中D的完成时间为28+22=50,而上面咱们计算的D的完成时间为57,50<57, 此轮不更新D的完成时间。
  • (7)、I进入拓扑序列,由I-21->D,可知,D在该路径中的完成时间为36+21=57。与以前D的完成时间相等,此轮也不更新D的完成时间。
  • (8)、G进入拓扑序列。有G-19->H能够求出,G路径上的H的完成时间为28+19=47。咱们以前计算的H的完成时间为44,因此本轮将H的完成时间更新为47。
  • (9)、H进入拓扑序列。有H-16->D能够求出本轮D的完成时间为47+16=63。63大于上面计算的D的完成时间57,因此将D的完成时间更新为63。
  • (10)、D进入拓扑序列,由于D为终点,因此拓扑排序结束,咱们最先完成时间也计算完毕。

  

    

通过上面这些步骤,上面数组中所存储的就是每一个结点的最先完成时间,以下所示:

  

2.计算最迟完成时间

上面由拓扑排序从前日后计算的完成时间就是咱们每一个结点的最先完成时间,接下来咱们将要计算每一个结点在总时间不变的状况下,最晚完成时间。每一个结点的最晚完成时间咱们要从后往前计算,由于整工程的总时间肯定,从后往前咱们就能够计算每一个结点最晚完成时间。下方就是计算最晚完成时间的全部的详细步骤。

由于咱们是按照拓扑排序的序列从后往前计算的最晚完成时间,因此咱们将拓扑序列从头至尾依次进入栈。而后以出栈的顺序来计算最晚完成时间,此刻的出栈顺序就是拓扑排序的逆序。因此下方计算每一个结点的最晚完成时间时要借助栈的数据结构来完成。和上述计算最先完成时间相似,依然是将完成时间存入数组中,而后根据咱们计算的数据进行更新完成时间。

下方是对最晚完成时间示例图的详细介绍:

  • (1):首先初始化咱们存储最晚完成时间的数组,由于整个工程的完成时间时63,因此咱们初始化每一个结点的最晚完成时间就以63为准。由于再晚也不会超过63。在该步骤中,咱们将D出栈。由于D是最后一个完成的结点, 因此其最晚完成时间就是63,咱们不作任何的更新。
  • (2):接着咱们将H出栈,有 H-16->D可知,H此刻的完成时间为 63-16= 47,更新H对应的完成时间。
  • (3):将G出栈,由 G-19->H能够计算出GH这条路径中G的完成时间为47-19=28,由 G-24->D这条路径能够计算出GD这条路径中G的完成时间为63-24=39。由于28<39, 为不耽误H的正常进行, 因此G此刻的最晚完成时间为28
  • (4):将I出栈,由 I-21->D这条路径,咱们能够计算出, 此刻I的最晚完成时间为63-21=42
  • (5):将C出栈,由 C-8->I能够计算出C在CI这条路径中最晚完成时间为42-8=34,由 C-22->D这条路径能够计算出CD这条路径中C的最晚完成时间为 63-22=41。由于34<41, 为了避免耽误I的完成,因此C的最晚完成时间为34
  • (6):将B出栈:有 B-16->G能够计算出,BG这条路径的最晚完成时间为 28-16=12,同理可计算出BI这条路径中B的最晚完成时间为42-12=30,BC这条路径中B的最晚完成时间为34-18=12。 因此B的最晚完成时间为12
  • (7):将E出栈,由 E-20->D中能够求出ED这条路径中E的最晚完成时间为63-20=43,同理可求出EH这条路径中E的最晚完成时间为47-7=40,最有E的最晚完成时间为40.
  • (8):将F出栈,由F-26->E可求出FE这条路径中F的最晚完成时间为 40-26=14,有F-17->G这条路径中能够求出FG这条路径中F的最晚完成时间为28-17=11,因此F的最晚完成时间为11。
  • (9):将A出栈,由A-10->B能够求出,AB这条路径中A的最晚完成时间为 12-10=2,同理AF这条路径中A的最晚完成时间为11-11=0,因此A的最晚完成时间为0。至此栈中的元素为空,咱们的最晚完成时间就计算完毕了,示例图以下所示。

  

  

通过上述步骤咱们就能够计算出每一个结点的最晚完成时间,以下所示:

  

 

3.计算关键路径

由每一个结点的最先完成时间和最晚完成时间咱们就能够计算出咱们的关键路径了。由于工程的总时间是固定的,那些最先完成时间等于最晚完成时间的结点就是咱们所要找的关键结点。下方就是在图遍历时,根据最先完成时间和最晚完成时间的对比,求出关键路径具体步骤。

  • (1):从最先和最晚完成时间中咱们能够看出来关键结点有 A, D, F, G, H。咱们能够在遍历图时给出这几个结点的前后顺序。
  • (2):从A结点开始遍历,A与F,B相连,F的最晚时间可最先完成时间相等,因此发展成关键路径, A-11->F
  • (3):F与E和G相连,G的最晚和最先完成时间等,因此此刻的关键路径为 A-11->F-17->G
  • (4):G与D和H相连,G的最晚时间是47-19=28获得的,因此此刻的关键路径为 A-11->F-17->G-17->H
  • (5):以此类推,能够计算出关键路径为 A-11->F-17->G-17->H-16->D

 

  

 

3、关键路径的代码实现

上面给出了关键路径的详细求解步骤,若是你将上面每一个步骤搞明白后,给出代码实现并不难。接下来咱们就会根据上面的步骤给出具体的代码实现。固然咱们依然使用Swift语言实现,固然使用的是当前Swift最新版本,也就Swift3.0

从上面的步骤中咱们能够大致分为三步:

  • 第一步:根据拓扑序列求出每一个结点最先完成时间。
  • 第二步:根据拓扑的逆序列,结合着最先完成时间求出每一个结点的最晚完成时间。
  • 第三步:结合着最先完成时间和最晚完成时间,根据图的结构求出关键路径。

接下来咱们的代码实现也是根据上面这三步来实现的。进入咱们代码实现的部分。

 

1.计算最先完成时间

本部分代码与上篇博客中拓扑排序的代码差很少,就多了下方红框中的部分。下方多出的代码就是在拓扑排序的过程当中求出每一个结点的最先完成时间,而后存储在earliestTimeOfVertex数组中。由于代码与拓扑排序的代码相似,因此在此就不作过多赘述了。

  

 

2.计算最晚完成时间

计算为最先完成时间后,咱们工程的整个工期也就是定了。根据这个固定的工期,而后结合着拓扑排序的倒序,就可求出每一个结点最晚完成的时间。下方这段代码就是计算每一个结点的最晚完成时间。就是从后往前计算。

首先将拓扑序列入栈,也就是将拓扑序列逆序的一个过程。而后不断从栈中取值,取一个结点就要计算该结点的最晚完成时间。与该结点相连结点的最晚时间 - 权值= 该结点的最晚完成时间。在这个过程当中取最小的哪一个时间,就是当前结点最晚完成的时间。具体代码以下所示:

 

3.计算关键路径

上面两步计算完最先完成时间和最晚完成时间后,接下来咱们就要开始计算咱们的关键路径了。下方代码其实就是在图的层次遍历时,查找那些最先完成时间与最晚完成时间相等的结点,若是相等,则是关键路径上的结点,而后将该节点进行输出。

固然下方代码中if后方的等式是个关键,将该等式翻译成文字就是:结点最先完成时间 == 下一个结点的最晚完成时间 - 该节点到下一个结点的权值 == 该结点最晚完成时间,若是上面这个等式成立,那么就说明该结点是关键结点,咱们将其进行输出。具体代码以下所示。

  

 

4.测试用例

上面三步是关键路径计算的全部代码,接下来又到了咱们测试的时刻了。下方就是咱们的测试用例,首先咱们根据图的结点和关系建立有向图。而后输出咱们建立的这个有向无环图。为了清晰的能看出每一步的执行,咱们并无将三步封装成一个函数来调用。下方的第一步就是求最先完成时间,第二步就是计算最晚完成时间,第三步就是计算咱们的关键路径了。

  

下方就是咱们的测试用例的输出结果了,输出结果仍是比较直观的,有图有真相,在此就不作过多赘述了。

  

 

好今天的博客就到这儿,下几篇博客依然是关于数据结构的,敬请期待。今天博客中的Demo依然会在github上进行分享。下方是分享地址。

github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/CriticalPath

相关文章
相关标签/搜索