关键路径

拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题。比如造一辆汽车,我们需要先造各种各样零件、部件,最终再组装成车,假如,造一个轮子需要0.5天时间,造一个发动机需要3天时间,造一个车底盘需要2天时间,造一个外壳需要2天时间,其他零部件时间需要2天,全部零部件集中到一处需要0.5天,组装成车需要2天,问汽车厂造辆车,最短需要多少时间呢?

一定不是时间的全部和。因此我们如果要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程,这个流程的时间就是最短时间。

因此在AOV网的基础上,我们来提出一个概念:

在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity  On Edge  Network)。我们把AOE网中没有入边的的顶点称为始点或源点,没有出边的顶点称为终点或汇点。由于一个工程,总有一个开始,一个结束,所以正常情况下,AOE网只有一个源点一个汇点。如下图

v9是汇点,v0是源点。v1~v9分别表示事件,弧<v0,v1>,<v0,v2>,…<v8,v9>都表示一个活动,用a0,a1,…a12表示,它们的值代表着活动持续的时间,比如弧<v0,v1>就是从源点开始的第一个活动a0,它的时间是3个单位。

既然AOE网表示工程流程,所以它就具有明显的工程特性。如有在某顶点所代表的事发生生,从该顶点出发的各活动才能开始。只有在进入某顶点的各活动都结束,该顶点所代表的事件才能发生。

尽管AOE与AOV网都是用来对工程建模的,但它们还是有很大的不同,主要体现在AOV网是顶点表示活动的网,它只描述活动之间的制约关系,而AOE网是用边表示活动的网,边上的权值表示活动持续的时间,因此,AOE网是要建立在活动之间制约关系没有矛盾的基础上,再来分析完成整个工程至少需要多少时间,或者为缩短完成工程所需要时间,应当加快哪些活动等问题。

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

最早开始时间与最晚开始时间不等,说明有空闲时间。也就是说,我们只需要找到所有活动的最早开始时间和最晚开始时间,并且比较它们,如果相等就意味着此活动是关键活动,活动间的路径为关键路径。如果不等就不是。为此有如下几个参数

1.    事件的最早发生时间evt(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的最晚发生时间,也就是不推迟工期的最晚开工时间。

我们是由1和2可以求得3和4,然后再根据ete[k]是否与lte[k]相等来判断ak是否是关键活动。

关键路径算法

我们将上面的AOE网转化为邻接表结构,注意增加了weight域,用来存储弧的权值

求事件 的最早发生时间etv的过程,就是我们从头至尾找拓扑序列的过程,因此,在求关键路径之前,需要先调用一次拓扑序列算法来计算etv和拓扑序列列表。为此我们首先在程序开始处声明几个全局变量。

int  *etv,*ltv;//事件最早发生时间和最迟发生时间数组int  *stack2;//用于存储拓扑序列的栈int   top2; //用于stack2的指针//下面是改进过的求拓扑序列算法status  TopologicalSort(GraphAdjList   GL){EdgeNode   *e;int   i,k,gettop;int   top=0;    //用于栈指针下标int    count=0;   //用于统计输出顶点的个数int     *stack;  //创栈将入度为0的顶点入栈stack=(int  *)malloc(GL->numVertexes*sizeof(int));for(i=0;i<GL->numVertexes;i++)if(0==GL->adjList[i].in) stack[++top]=i;top2=0;etv=(int  *)malloc(GL-numVertexes*sizeof(int)); //事件最早发生时间for(i=0;i<GL->numVertexes;i++)etv[i] = 0;stack2=(int  *)malloc(GL->numVertexes*sizeof(int));while(top!=0){gettop = stack[top--];count++;stack2[++top2] = gettop; //将弹出的顶点序号压入拓扑序列的栈for(e=GL->adjList[gettop].firstedge;e;e=e->next){k=e->adjvex;if(!(--GL->adjList[k].in))stack[++top]=k;if((etv[gettop]+e->weight)>etv[k])etv[k]=etv[gettop]+e->weight;}}if(count<GL->numVertexes)return ERROR;elsereturn OK;}

代码中,除了加粗部分外,与前面讲的拓扑排序算法没有什么不同,第11~15行为初始化全局变量etv数组、top2和stack2的过程。第21行就是将本是要输出的拓扑序列压入全局栈stack2中。第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]的最早发生时间公式是:

下面我们来看一下求关键路径的算法代码

void  CriticalPath(GraphAdjList   GL){EdgeNode   *e;int   i,gettop,k,j;int ete,lte;TopologicalSort(GL);   //求拓扑序列,计算数组etv和stack2的值ltv=(int  *)malloc(GL->numVertexes*sizeof(int));//事件最晚发生时间for(i=0;i<GL->numVertexes;i++)ltv[i] =etv[GL->numVertexes-1];//初始化ltvwhile(top2!=0){gettop = stack2[top2-1];for(e=GL->adjList[gettop].firstedge;e;e=e->next){//求各顶点事件的最迟发生时间ltv值k=e->adjvex;if(ltv[k]-e->weight<ltv[gettop])//各点事件最晚发生时间ltv[gettop] =ltv[k]-e->weight;}for(j=0;j<GL->numVertexes;j++){for(e=GL->adjList[j].firstedge;e;e=e->next){k=e->adjvex;ete = etv[j];lte =ltv[k]-e->weight;if(ete==lte)printf(“<v%d,v%d>length:%d,”,GL->adjList[j].data,GL->adjList[k].data,e->weight);}}}}

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

2.    第6行,调用求拓扑序列的函数。执行完毕后,全局变量数组etv和栈stack值。top2=10。也就是说,对于每个事件的最早发生时间,我们已经计算出来了

3.    第7~9行为初始化全局变量ltv数组,因为etv[9]=27,所以数组ltv当前的值为:{27, 27, 27,27, 27, 27, 27, 27, 27}

4.    第10~19行为计算ltv的循环。第12行,先将stack2的栈头出栈,由后进先出得到gettop = 0。根据邻接表中,v9没有弧表,所以第13~18行循环体未执行。

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、13、25,此时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)

此时你应该发现,我们在计算ltv时,其实是把拓扑序倒过来进行的。因此我们可以得出计算顶点vk即求ltv[k]的最晚发生时间的公式是:

就这样,当程序执行到第20行时,相关变量的值如图

相关变量的值上表,如果单位是天的话,比如etv=[1]而ltv[7],表示的意思就是如果时间单位是天的话,哪怕v1这个事件在第7天才开始,也可以保证整个工程的按期完成,你可以提前v1事件开始时间,但你最早也只能在第3三天开始。

8.    第20~31行是来求另两个变量活动最早开始时间ete和活动最晚开始时间lte,并对相同下标的它们做比较。两重循环嵌套是对邻接表的顶点和每个顶点的弧表遍历

9.    当j=0时,从v0点开始,有<v0,v2>和<v0,v1>两条弧。当k=2时,ete=etv[j]=etv[0]。lte=ltv[v]-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[1]-len<v0,v1>=7-3=4,此时ete≠lte。因此<v0,v1>并不是关键活动

这里需要解释一下,ete本来是表示活动<vk,vj>的最早开工时间,是针对弧来说的。但是只有此弧尾顶点vk的事件发生了,它才可以开始,因此ete=etv[k]

而lte表示的是活动<vk,vj>的最晚开工时间,但此活动再晚也不能等于vj事件发生才开始,而必须要在vj事件之前发生,所以lte=ltv[j]-len<vk,vj>。

比如晚上23点睡觉,你不能说到23点才开始做作业,而必须要提前2小时,在21点开始,才有可能按时完成作业。

所以最终,其实就是判断ete与lte是否相等,相等意味着活动没有任何空闲,是关键活动,否则就不是。

10.  j=1一直到j=9为止,做法完全相同的,关键路径打印结果<v0,v2>4,<v2,v3>8<v3,v4>3,<v4,v7>4,<v7,v8>5,<v8,v9>3,最终关键路径为如下图

数据结构与算法22-图的关键路径相关推荐

  1. 数据结构与算法之图的应用

    数据结构与算法之图的应用 图的定义和基本概念 图的实现 数组〈邻接矩阵〉 邻接表 图的应用 最小生成树 Prim(普里姆)算法 Kruskal(克鲁斯卡尔)算法 最短路径 迪克斯特拉算法 拓扑排序 执 ...

  2. python函数结构图_Python数据结构与算法之图结构(Graph)实例分析

    本文实例讲述了Python数据结构与算法之图结构(Graph).分享给大家供大家参考,具体如下: 图结构(Graph)--算法学中最强大的框架之一.树结构只是图的一种特殊情况. 如果我们可将自己的工作 ...

  3. python define graph_Python数据结构与算法之图结构(Graph)实例分析

    本文实例讲述了Python数据结构与算法之图结构(Graph).分享给大家供大家参考,具体如下: 图结构(Graph)--算法学中最强大的框架之一.树结构只是图的一种特殊情况. 如果我们可将自己的工作 ...

  4. python棋盘最短路径_Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例...

    本文实例讲述了Python数据结构与算法之图的最短路径(Dijkstra算法).分享给大家供大家参考,具体如下: # coding:utf-8 # Dijkstra算法--通过边实现松弛 # 指定一个 ...

  5. 特征图注意力_从数据结构到算法:图网络方法初探

    作者 | 朱梓豪 来源 | 机器之心 原文 | 从数据结构到算法:图网络方法初探 如果说 2019 年机器学习领域什么方向最火,那么必然有图神经网络的一席之地.其实早在很多年前,图神经网络就以图嵌入. ...

  6. 数据结构与算法之-----图(拓扑排序)

    [​​​​​​​ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据 ...

  7. 数据结构与算法之-----图(搜索算法)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  8. 数据结构与算法--符号图

    数据结构与算法--符号图 为了计算简单,传统的图中,都是使用整数来表示顶点,这样难免会有点抽象,不能直接反映各个顶点代表的信息.在实际生活中,使用图时一般会建立一个一一对应的关系表,如下 顶点 0 1 ...

  9. 数据结构与算法之-----图(代码实现)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己构建的,未使用 ...

  10. js数据结构和算法(8)-图

    8-图(第11章) 8.1 图的定义 图是一种非线性结构,由一系列顶点及其连接顶点的边组成.比如A和B.A和D是相邻的,而A和E不是相邻的.一个顶点相邻顶点的数量叫作度,比如A的度为3.D的度为4.路 ...

最新文章

  1. (iOS-基本知识)Category VS Extension 原理详解
  2. vivo手机解锁_关于手机“解锁”的话题,vivo屏幕指纹技术很有话语权
  3. MATLAB三维曲面绘图你不得不知道的...
  4. java蓝桥杯凑算是,第七届蓝桥杯JAVA B组真题解析-凑算式(第三题)
  5. 分布式防火墙技术及主要特点
  6. Linux上静态库和动态库的编译和使用
  7. php自己创建函数,php自定义函数之内部函数
  8. linux磁盘写保护怎么修改_linux系统怎么避免u盘被写保护
  9. 【AI面试题】Kmeams算法流程以及Kmeans++介绍
  10. Linux开机启动过程(10):start_kernel 初始化(至setup_arch初期)
  11. 软件测试--中间件介绍
  12. 嵌入式操作系统内核原理和开发(基础)
  13. MATLAB信号处理之信号的采样
  14. 剑指offer面试题[12]-打印1到最大的n位数
  15. apache的站点安全1
  16. 汉字字符集编码查询 unicode编码查询
  17. f1c100linux系统吗,全志F1C100s怎么样 F1C100s芯片参数介绍
  18. matlab插值函数的作用,matlab 插值函数
  19. 基于selenium的大麦网自动抢票脚本实测(新年第一篇)
  20. 音诺恒RK3568高性能智能商显安卓广告机主板解决方案

热门文章

  1. 【自定义控件】五角星、蜘蛛网
  2. Android系统模拟鼠标键盘详解
  3. AP+AC旁挂式组网(简单易懂!新手必看!)
  4. java乐观锁实现案例
  5. 处理可预料的异常处理
  6. 汇编语言程序设计DOSBox+MASM文件,解决汇编程序设计入门问题
  7. 阿里云短信服务签名和申请说明怎么填
  8. taobao.logistics.dummy.send( 无需物流发货处理 )接口,淘宝r2接口,淘宝oAu2.0接口,淘宝订单发货接口
  9. Meme大街掀起的FOMO——Vitalik VS SHIBA
  10. sat数学可以用计算机吗,原来SAT数学真没想象中那么简单!