在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
上篇博客我们介绍了AOV网的拓扑序列,请参考《因为拓扑序列是一个串行序列,如果按照该序列执行项目,那么就是串行执行的。我们知道在一个项目中的一些子工程是可以并行来完成的,这也就类似我们的多线程。今天我们要解决的问题就是找出一个关键路径,是工期最优并保证工程的完成。什么是关键路径,我们在下方会进行详细介绍。
一、关键路径概述 在聊关键路径之前,我们先看一个简单的实例,如下图所示。我们将下方这个有向无环图看做是整个工程,将每个节点看做是该项目工程的一个子工程。子工程间又有一定的优先级。在下方图中,A的优先级最高。A做完后,B、C才可以进行开发。B、C完成后,我们才可以开发D。从下图中我们不难看出,该图的拓扑序列为A, B, C, D。如果我们按照串行的方式来完成此工程的话,那么工程完成的顺序可以是A-5->B, A-8->C, B-3->D, C-10->D。总时间为26。 从上面这个序列中我们显然可以看出来这不是最优的,因为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。
二、关键路径算法的具体步骤 第一部分因为示例比较简单,算是我们本篇博客的开胃小菜,接下来进入我们本篇博客真正的主题。在本部分,我们还是以原理图为主,本部分不会给出具体的代码实现,我们只讲原理。本篇博客是在上篇博客的基础上实现的,因为每个路径的最早执行的时间的计算依赖于拓扑序列,所以我们依然会采用上篇博客我们拓扑序列所使用的图的结构。下方就是我们要求关键路径的有向无环图。如果你看过前几天博客的话,那么对下方这个图的结构应该是非常熟悉了吧,今天我们依然会使用下方这个图来做我们的实例。
1.最早完成时间计算 首先我们根据拓扑排序的过程来计算出每个结点最早完成时间。最早完成时间计算的计算过程就是在拓扑排序的过程中添加一段记录每个结点完成的最早时间,下方就是求最早完成时间的整个实例图,下方会给出每一步的详细介绍。下方是由拓扑排序计算最早完成时间的具体步骤,并且给出了每一步的计算规则。 其实下方这个步骤与上一篇博客中拓扑排序的步骤大同小异,只是在其基础上引入了一个数组。数组中记录的就是索引对应结点的最早完成时间,具体步骤如下所示。下方的每一步其实就是拓扑排序的步骤,只是加入了每个结点最早完成时间的计算,因为上篇博客对拓扑排序做了详细的叙述,在此就不做过多赘述了。
经过上面这些步骤,上面数组中所存储的就是每个结点的最早完成时间,如下所示:
2.计算最迟完成时间 上面由拓扑排序从前往后计算的完成时间就是我们每个结点的最早完成时间,接下来我们将要计算每个结点在总时间不变的情况下,最晚完成时间。每个结点的最晚完成时间我们要从后往前计算,因为整工程的总时间确定,从后往前我们就可以计算每个结点最晚完成时间。下方就是计算最晚完成时间的所有的详细步骤。 因为我们是按照拓扑排序的序列从后往前计算的最晚完成时间,所以我们将拓扑序列从头到尾依次进入栈。然后以出栈的顺序来计算最晚完成时间,此刻的出栈顺序就是拓扑排序的逆序。所以下方计算每个结点的最晚完成时间时要借助栈的数据结构来完成。和上述计算最早完成时间类似,依然是将完成时间存入数组中,然后根据我们计算的数据进行更新完成时间。 下方是对最晚完成时间示例图的详细介绍:
经过上述步骤我们就可以计算出每个结点的最晚完成时间,如下所示:
3.计算关键路径 由每个结点的最早完成时间和最晚完成时间我们就可以计算出我们的关键路径了。因为工程的总时间是固定的,那些最早完成时间等于最晚完成时间的结点就是我们所要找的关键结点。下方就是在图遍历时,根据最早完成时间和最晚完成时间的对比,求出关键路径具体步骤。
三、关键路径的代码实现 上面给出了关键路径的详细求解步骤,如果你将上面每个步骤搞明白后,给出代码实现并不难。接下来我们就会根据上面的步骤给出具体的代码实现。当然我们依然使用Swift语言实现,当然使用的是当前Swift最新版本,也就Swift3.0。 从上面的步骤中我们可以大体分为三步:
接下来我们的代码实现也是根据上面这三步来实现的。进入我们代码实现的部分。
1.计算最早完成时间 本部分代码与上篇博客中拓扑排序的代码差不多,就多了下方红框中的部分。下方多出的代码就是在拓扑排序的过程中求出每个结点的最早完成时间,然后存储在earliestTimeOfVertex数组中。因为代码与拓扑排序的代码类似,所以在此就不做过多赘述了。
2.计算最晚完成时间 计算为最早完成时间后,我们工程的整个工期也就是定了。根据这个固定的工期,然后结合着拓扑排序的倒序,就可求出每个结点最晚完成的时间。下方这段代码就是计算每个结点的最晚完成时间。就是从后往前计算。 首先将拓扑序列入栈,也就是将拓扑序列逆序的一个过程。然后不断从栈中取值,取一个结点就要计算该结点的最晚完成时间。与该结点相连结点的最晚时间 - 权值= 该结点的最晚完成时间。在这个过程中取最小的哪个时间,就是当前结点最晚完成的时间。具体代码如下所示:
3.计算关键路径 上面两步计算完最早完成时间和最晚完成时间后,接下来我们就要开始计算我们的关键路径了。下方代码其实就是在图的层次遍历时,查找那些最早完成时间与最晚完成时间相等的结点,如果相等,则是关键路径上的结点,然后将该节点进行输出。 当然下方代码中if后方的等式是个关键,将该等式翻译成文字就是:结点最早完成时间 == 下一个结点的最晚完成时间 - 该节点到下一个结点的权值 == 该结点最晚完成时间,如果上面这个等式成立,那么就说明该结点是关键结点,我们将其进行输出。具体代码如下所示。
4.测试用例 上面三步是关键路径计算的所有代码,接下来又到了我们测试的时刻了。下方就是我们的测试用例,首先我们根据图的结点和关系创建有向图。然后输出我们创建的这个有向无环图。为了清晰的能看出每一步的执行,我们并没有将三步封装成一个函数来调用。下方的第一步就是求最早完成时间,第二步就是计算最晚完成时间,第三步就是计算我们的关键路径了。
下方就是我们的测试用例的输出结果了,输出结果还是比较直观的,有图有真相,在此就不做过多赘述了。
好今天的博客就到这儿,下几篇博客依然是关于数据结构的,敬请期待。今天博客中的Demo依然会在github上进行分享。下方是分享地址。 github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/CriticalPath
|
请发表评论