算法与数据结构(八) AOV网的关键路径
上篇博客我们介绍了AOV网的拓扑序列,请参考《数据结构(七) AOV网的拓扑排序(Swift面向对象版)》。拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的,但是工期不是最优的。因为拓扑序列是一个串行序列,如果按照该序列执行项目,那么就是串行执行的。我们知道在一个项目中的一些子工程是可以并行来完成的,这也就类似我们的多线程。今天我们要解决的问题就是找出一个关键路径,是工期最优并保证工程的完成。什么是关键路径,我们在下方会进行详细介绍。
一、关键路径概述
在聊关键路径之前,我们先看一个简单的实例,如下图所示。我们将下方这个有向无环图看做是整个工程,将每个节点看做是该项目工程的一个子工程。子工程间又有一定的优先级。在下方图中,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.最早完成时间计算
首先我们根据拓扑排序的过程来计算出每个结点最早完成时间。最早完成时间计算的计算过程就是在拓扑排序的过程中添加一段记录每个结点完成的最早时间,下方就是求最早完成时间的整个实例图,下方会给出每一步的详细介绍。下方是由拓扑排序计算最早完成时间的具体步骤,并且给出了每一步的计算规则。
其实下方这个步骤与上一篇博客中拓扑排序的步骤大同小异,只是在其基础上引入了一个数组。数组中记录的就是索引对应结点的最早完成时间,具体步骤如下所示。下方的每一步其实就是拓扑排序的步骤,只是加入了每个结点最早完成时间的计算,因为上篇博客对拓扑排序做了详细的叙述,在此就不做过多赘述了。
(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=22,C的完成时间为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。
三、关键路径的代码实现
上面给出了关键路径的详细求解步骤,如果你将上面每个步骤搞明白后,给出代码实现并不难。接下来我们就会根据上面的步骤给出具体的代码实现。当然我们依然使用Swift语言实现,当然使用的是当前Swift最新版本,也就Swift3.0。
从上面的步骤中我们可以大体分为三步:
第一步:根据拓扑序列求出每个结点最早完成时间。
第二步:根据拓扑的逆序列,结合着最早完成时间求出每个结点的最晚完成时间。
第三步:结合着最早完成时间和最晚完成时间,根据图的结构求出关键路径。
接下来我们的代码实现也是根据上面这三步来实现的。进入我们代码实现的部分。
1.计算最早完成时间
本部分代码与上篇博客中拓扑排序的代码差不多,就多了下方红框中的部分。下方多出的代码就是在拓扑排序的过程中求出每个结点的最早完成时间,然后存储在earliestTimeOfVertex数组中。因为代码与拓扑排序的代码类似,所以在此就不做过多赘述了。
2.计算最晚完成时间
计算为最早完成时间后,我们工程的整个工期也就是定了。根据这个固定的工期,然后结合着拓扑排序的倒序,就可求出每个结点最晚完成的时间。下方这段代码就是计算每个结点的最晚完成时间。就是从后往前计算。
首先将拓扑序列入栈,也就是将拓扑序列逆序的一个过程。然后不断从栈中取值,取一个结点就要计算该结点的最晚完成时间。与该结点相连结点的最晚时间 - 权值= 该结点的最晚完成时间。在这个过程中取最小的哪个时间,就是当前结点最晚完成的时间。具体代码如下所示:
3.计算关键路径
上面两步计算完最早完成时间和最晚完成时间后,接下来我们就要开始计算我们的关键路径了。下方代码其实就是在图的层次遍历时,查找那些最早完成时间与最晚完成时间相等的结点,如果相等,则是关键路径上的结点,然后将该节点进行输出。
当然下方代码中if后方的等式是个关键,将该等式翻译成文字就是:结点最早完成时间 == 下一个结点的最晚完成时间 - 该节点到下一个结点的权值 == 该结点最晚完成时间,如果上面这个等式成立,那么就说明该结点是关键结点,我们将其进行输出。具体代码如下所示。
4.测试用例
上面三步是关键路径计算的所有代码,接下来又到了我们测试的时刻了。下方就是我们的测试用例,首先我们根据图的结点和关系创建有向图。然后输出我们创建的这个有向无环图。为了清晰的能看出每一步的执行,我们并没有将三步封装成一个函数来调用。下方的第一步就是求最早完成时间,第二步就是计算最晚完成时间,第三步就是计算我们的关键路径了。
下方就是我们的测试用例的输出结果了,输出结果还是比较直观的,有图有真相,在此就不做过多赘述了。
好今天的博客就到这儿,下几篇博客依然是关于数据结构的,敬请期待。今天博客中的Demo依然会在github上进行分享。下方是分享地址。
github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/CriticalPath
算法与数据结构(八) AOV网的关键路径的更多相关文章
- 算法与数据结构(八) AOV网的关键路径(Swift版)
上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...
- 算法与数据结构(七) AOV网的拓扑排序
今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...
- 算法与数据结构(七) AOV网的拓扑排序(Swift版)
今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的.拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍: AOV网:在现代化管理中,人们常用有向图来描述和分析一项工程的计划 ...
- 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树
http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...
- 数据结构关于AOV与AOE网的区别
AOV网,顶点表示活动,弧表示活动间的优先关系的有向图. 即如果a->b,那么a是b的先决条件. AOE网,边表示活动,是一个带权的有向无环图, 其中顶点表示事件,弧表示活动,权表示活动持续时间 ...
- AOE网与关键路径简介
前面我们说过的拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题.如果我们要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程, ...
- 有向无环图的应用—AOV网 和 拓扑排序
有向无环图:无环的有向图,简称 DAG (Directed Acycline Graph) 图. 一个有向图的生成树是一个有向树,一个非连通有向图的若干强连通分量生成若干有向树,这些有向数形成生成森林 ...
- 基于AOE网的关键路径的求解
[1]关键路径 在我的经验意识深处,“关键”二字一般都是指临界点. 凡事万物都遵循一个度的问题,那么存在度就会自然有临界点. 关键路径也正是研究这个临界点的问题. 在学习关键路径前,先了解一个AOV网 ...
- AOV网与拓扑排序
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity on Vextex Network).AOV网中的弧表示活动 ...
随机推荐
- CMS模板应用调研问卷
截止目前,已经有数十家网站与我们合作,进行了MIP化改造,在搜索结果页也能看到"闪电标"的出现.除了改造方面的问题,MIP项目组被问到最多的就是:我用了wordpress,我用了织 ...
- 模拟AngularJS之依赖注入
一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...
- PHP中遍历XML之SimpleXML
简单来讲述一些XML吧,XML是可扩展标记语言,是一种用于标记电子文件使其具有结构性的标记语言.XML是当今用于传输数据的两大工具之一,另外一个是json. 我们在PHP中使用XML也是用来传输数据, ...
- JQuery(2)
JQuery下拉框操作: 取值赋值操作 body代码: <select id="sel"> <option value="北京">北京& ...
- Android 工具-adb
Android 工具-adb 版权声明:本文为博主原创文章,未经博主允许不得转载. Android 开发中, adb 是开发者经常使用的工具,是 Android 开发者必须掌握的. Android D ...
- WPF中Grid实现网格,表格样式通用类
/// <summary> /// 给Grid添加边框线 /// </summary> /// <param name="grid"></ ...
- Boost信号/槽signals2
信号槽是Qt框架中一个重要的部分,主要用来解耦一组互相协作的类,使用起来非常方便.项目中有同事引入了第三方的信号槽机制,其实Boost本身就有信号/槽,而且Boost的模块相对来说更稳定. signa ...
- 敏捷转型历程 - Sprint3 Planning
我: Tech Leader 团队:团队成员分布在两个城市,我所在的城市包括我有4个成员,另外一个城市包括SM有7个成员.另外由于我们的BA离职了,我暂代IT 的PO 职位.PM和我在一个城市,但他不 ...
- Linux虚拟化学习笔记<一>
关于虚拟化,原理的东西是非常复杂的,要想完全理解,没有足够的耐心是不不能完全学透这部分内容的.那下面我主要以资源汇总的形式把一些资料罗列出来,帮助大家快速理解虚拟化,快速使用和配置. 为什么要虚拟化: ...
- Help Hanzo (素数筛+区间枚举)
Help Hanzo 题意:求a~b间素数个数(1 ≤ a ≤ b < 231, b - a ≤ 100000). (全题在文末) 题解: a~b枚举必定TLE,普通打表MLE,真是头疼 ...