数据结构之[关键路径]以及[拓扑算法优化]
【1】关键路径(说明部分参考于:https://www.cnblogs.com/Braveliu/p/3461649.html)
在我的经验意识深处,“关键”二字一般都是指临界点。
凡事万物都遵循一个度的问题,那么存在度就会自然有临界点。
关键路径也正是研究这个临界点的问题。
在学习关键路径前,先了解一个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 为止,做法完全相同。
最终关键路径如下图所示:
注意:本例是唯一一条关键路径,并不等于不存在多条关键路径。
如果是多条关键路径,则单是提高一条关键路径上的关键活动速度并不是能导致整个工程缩短工期、
而必须提高同时在几条关键路径上的活动的速度。
代码如下:
1 #include "stdafx.h" 2 #include<iostream> 3 #include<string> 4 using namespace std; 5 #define MAXSIZE 100 6 #define ERROR 0 7 #define OK 1 8 #define INFINITY 65535 9 typedef int Status; 10 11 int *etv, *ltv; //事件最早发生时间和最迟发生时间数组,全局变量 12 int *stack2; //用于存储拓扑序列的栈 13 int top2; //用于stack2的指针 14 15 typedef struct ArcNode //边表结点 16 { 17 int adjvex; //改变所指向的顶点的位置 18 int weight; //用于存储权值 19 struct ArcNode *nextarc; //指向下一条边的指针 20 }ArcNode; 21 typedef struct VNode //顶点表结点 22 { 23 int in; 24 char data; //顶点域,存储顶点信息 25 ArcNode *firstarc; //指向第一条依附该顶点的边的指针 26 }VNode, AdjList[MAXSIZE]; //AdList表示邻接表类型 27 typedef struct //邻接表 28 { 29 AdjList vertices; 30 int vexnum, arcnum; //图的当前顶点数和边数 31 }ALGraph; 32 33 typedef struct 34 { 35 char vexs[MAXSIZE]; //顶点表 36 int arcs[MAXSIZE][MAXSIZE]; //邻接矩阵 37 int vexnum, arcnum; //图的当前点数和边数 38 }AMGraph; 39 40 void CreateUDN(AMGraph &G) //采用邻接矩阵表示法,创建无向网&G 41 { 42 int i, j, w; 43 std::cout << "请输入总顶点数、总边数(空格隔开):" << endl; 44 cin >> G.vexnum >> G.arcnum; //输入总顶点数、总边数 45 std::cout << "请输入顶点信息(空格隔开):" << endl; 46 for (i = 0; i < G.vexnum; i++) //依次输入点的信息 47 { 48 cin >> G.vexs[i]; 49 } 50 for (i = 0; i < G.vexnum; i++) //初始化邻接矩阵,编的权值均为极大值MaxInt 51 for (j = 0; j < G.vexnum; j++) 52 { 53 if (i == j) 54 G.arcs[i][j] = 0; 55 else 56 G.arcs[i][j] = INFINITY; 57 } 58 std::cout << "请输入边的信息(输入顺序:连接点1编号、连接点2编号以及权值):" << endl; 59 for (int k = 0; k < G.arcnum; k++) //构造邻接矩阵 60 { 61 cin >> i >> j >> w; //输入一条边依附的顶点 62 G.arcs[i - 1][j - 1] = w; 63 } 64 65 } 66 67 void CreateALGraph(AMGraph G, ALGraph &GL) 68 { 69 int i, j; 70 ArcNode *e; 71 GL.vexnum = G.vexnum; 72 GL.arcnum = G.arcnum; 73 for (i = 0; i <G.vexnum; i++) //读入顶点信息,建立顶点表 74 { 75 GL.vertices[i].in = 0; 76 GL.vertices[i].data = G.vexs[i]; 77 GL.vertices[i].firstarc = NULL;//将边表置为空表 78 } 79 80 for (i = 0; i<G.vexnum; i++) //建立边表 81 { 82 for (j = 0; j<G.vexnum; j++) 83 { 84 if (G.arcs[i][j] != 0 && G.arcs[i][j]<INFINITY) 85 { 86 e = new ArcNode; 87 e->adjvex = j; //邻接序号为j 88 e->weight = G.arcs[i][j]; 89 e->nextarc = GL.vertices[i].firstarc; //将当前顶点上的指向的结点指针赋值给e 90 GL.vertices[i].firstarc = e; //将当前顶点的指针指向e 91 GL.vertices[j].in++; 92 } 93 } 94 } 95 } 96 97 //拓扑排序 98 Status TopologicalSort(ALGraph GL) 99 { //若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0 100 ArcNode *e; 101 int i, k, gettop; 102 int top = 0; //用于栈指针下标 103 int count = 0; //用于统计输出顶点的个数 104 int *stack; /* 建栈将入度为0的顶点入栈 */ 105 stack = new int[GL.vexnum]; 106 for (i = 0; i<GL.vexnum; i++) 107 if (0 == GL.vertices[i].in) //将入度为0的顶点入栈 108 stack[++top] = i; 109 110 top2 = 0; 111 etv = new int[GL.vexnum]; //事件最早发生时间数组 112 for (i = 0; i<GL.vexnum; i++) 113 etv[i] = 0; //初始化 114 stack2 = new int[GL.vexnum]; //初始化拓扑序列栈 115 116 std::cout << "TopologicalSort: "; 117 while (top != 0) 118 { 119 gettop = stack[top--]; 120 std::cout << GL.vertices[gettop].data << " -> "; 121 count++; //输出i号顶点,并计数 122 123 stack2[++top2] = gettop; //将弹出的顶点序号压入拓扑序列的栈 */ 124 125 for (e = GL.vertices[gettop].firstarc; e; e = e->nextarc) 126 { 127 k = e->adjvex; 128 if (!(--GL.vertices[k].in))//将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 129 stack[++top] = k; 130 131 if ((etv[gettop] + e->weight)>etv[k]) //求各顶点事件的最早发生时间etv值 132 etv[k] = etv[gettop] + e->weight; 133 } 134 } 135 std::cout << endl; 136 if (count < GL.vexnum) 137 return ERROR; 138 else 139 return OK; 140 } 141 142 //求关键路径,GL为有向网,输出G的各项关键活动 143 void CriticalPath(ALGraph GL) 144 { 145 ArcNode *e; 146 int i, gettop, k, j; 147 int ete, lte; //声明活动最早发生时间和最迟发生时间变量 148 TopologicalSort(GL); //求拓扑序列,计算数组etv和stack2的值 149 ltv = new int[GL.vexnum]; //事件最早发生时间数组 150 for (i = 0; i<GL.vexnum; i++) 151 ltv[i] = etv[GL.vexnum - 1]; //初始化 152 153 std::cout << "etv: "; 154 for (i = 0; i < GL.vexnum; i++) 155 std::cout << etv[i] << "->"; 156 std::cout << endl; 157 158 while (top2 != 0) //出栈是求ltv 159 { 160 gettop = stack2[top2--]; 161 for (e = GL.vertices[gettop].firstarc; e; e = e->nextarc)//求各顶点事件的最迟发生时间ltv值 162 { 163 k = e->adjvex; 164 if (ltv[k] - e->weight < ltv[gettop]) 165 ltv[gettop] = ltv[k] - e->weight; 166 } 167 } 168 169 std::cout<<"ltv: "; 170 for (i = 0; i<GL.vexnum; i++) 171 std::cout << ltv[i] << "->"; 172 std::cout << endl; 173 174 for (j = 0; j<GL.vexnum; j++) //求ete,lte和关键活动 175 { 176 for (e = GL.vertices[j].firstarc; e; e = e->nextarc) 177 { 178 k = e->adjvex; 179 ete = etv[j]; //活动最早发生时间 180 lte = ltv[k] - e->weight; //活动最迟发生时间 181 if (ete == lte) //两者相等即在关键路径上 182 std::cout << "<" << GL.vertices[j].data << " - " << GL.vertices[k].data << ">," << "Length:" << e->weight << endl; 183 } 184 } 185 } 186 int main() 187 { 188 AMGraph G; 189 ALGraph GL; 190 CreateUDN(G); 191 CreateALGraph(G,GL); 192 CriticalPath(GL); 193 return 0; 194 }
测试结果:
转载于:https://www.cnblogs.com/Trojan00/p/9021613.html
数据结构之[关键路径]以及[拓扑算法优化]相关推荐
- 数据结构与算法(5)字符串(BF算法、KMP算法及KMP算法优化)
目录 一.BF算法(暴力算法) 二.KMP算法 三.KMP算法优化 一.BF算法(暴力算法) 一个一个往后匹配,匹配失败继续从母串下一个和头(子串的头)往后继续匹配. 虽然简单,但是需要较多的时间复杂 ...
- 数据结构C++——关键路径
数据结构C++--关键路径 文章目录 数据结构C++--关键路径 一.前言 二.关键路径的概念 三.关键路径的实现 ①关键路径的实现原理 ②关键路径的代码实现 ③测试的全部代码 四.总结 一.前言 理 ...
- 【学习笔记】比较分别用prim和kruskal实现最小生成树和算法优化方案
kruskal: 1.思路 :设G=(V,E)是无向连通带权图,V={,-,n}:设最小生成树T=(V,TE),该树的初始状态为只有n个顶点而无边的非连通图T=(V,{}),Kruskal算法将这n个 ...
- NVDKC6416平台H.264算法优化
本文转载自:http://blog.csdn.net/embedesign/archive/2009/09/15/4556486.aspx,版权归原作者,编辑:小乙哥 多媒体通信终端设备具有广泛的应用 ...
- Bloom Filter算法优化
Bloom Filter算法优化 基于Redis的Bloom Filter去重,利用Redis的String数据结构,但Redis的String最大只能512M,所以数据过大的时候,需要申请多个去重块 ...
- 智源青年科学家袁洋:机器学习可靠性与算法优化的方法探索
2020年2月11日,在"智源论坛Live | 青年科学家线上报告会"上,智源青年科学家.清华大学助理教授袁洋作了题为<机器学习可靠性与算法优化>的演讲.袁洋,2018 ...
- 数据结构与算法教程,让数据结构不再难懂,让算法不再难写
据结构与算法不分家 数据结构包括数据对象集以及它们在计算机中的组织方式,即它们的逻辑结构和物理存储结构,一般我们可以认为数据结构指的是一组数据的存储结构. 算法就是操作数据的方法,即如何操作数据效率更 ...
- 算法优化---向量数组计算替代元素级别判断
算法优化---向量数组计算替代判断 目录 前言 元素级别迭代与series,ndarray的迭代 测试 series, ndarray之间的数学计算,替代元素遍历判断. 根据数量级大小,选择适合的算法 ...
- 从一道笔试题谈算法优化(下)
因为受到经济危机的影响,我在 bokee.com 的博客可能随时出现无法访问的情况:因此将2005年到2006年间在 bokee.com 撰写的博客文章全部迁移到 csdn 博客中来,本文正是其中一篇 ...
最新文章
- SCCM 2012 R2---安装SCCM 2012 R2
- 成长与迁移,全球半导体格局演变
- NOVA 日志报错解决办法
- 第23天:指导与管理项目工作和4种合同的区别
- 为Android应用程序添加社会化分享功能
- 去掉左边0_SLAM从0到1——11. 视觉里程计VO内容框架
- lru调度算法例题_嵌入式必会!C语言最常用的贪心算法就这么被攻略了
- python中类和对象_Python里的类和对象简介
- 堆排序工具类(适用于top k问题,java泛型实现)
- JustOJ1500: 蛇行矩阵
- nodejs路由控制图文混排
- 图像分割(一):K-means聚类算法
- Linux MPLS 总结
- python语音识别_Python语音识别终极指南
- CSS3 calc() 用法
- 310569138 294609417 297440781 猪八戒上的骗子
- JavaScript中的mouseover与mouseenter,mouseout和mouseleave的区别
- C++源代码单词扫描程序(词法分析)
- 电路设计布线技巧十规则
- TM1638的一些使用以及点亮数码管程序
热门文章
- USACO 1.3.1 挤牛奶
- 【论文阅读笔记】Learning To Detect Unseen Object Classes by Between-Class Attribute Transfer
- 为什么院士们纷纷推荐这本书?​
- 计算机word知识点小技巧,史上最全的Word技巧大全 掌握这些你也能成为Word高手...
- vscode运行Live Server报错:Windows找不到文件‘chrome‘,请确定文件名是否正确后,再试一次。
- Java常用算法——迭代 递归篇
- 让 Python 拥有 C/C++ 一样的速度,编译神器 Codon 发布!
- 盘点行业 洞察大势 挥斥方遒 指点江山 洞见2018中国HR服务峰会惊艳业界
- 「全屋智能」云米全屋互联网家电 vs 绿米全屋智能产品+服务
- 烤仔建工首支元宇宙Vlog上线!