关键路径——CriticalPath算法
背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的。该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解。
关键路径:
即决定一项工程的完成时间的路径。
如下图所示,是一辆汽车的生产流程,其中外壳、发动机、轮子等的生产过程都是可以并行进行的,但是发送机生产需要的时间最长,而只有所有零部件生产完成才才能进行下一步,因此图中用红色加粗的那一条路径即为该工程的关键路径(即决定工程的实际完成时间的路径)。
CriticalPath算法理解:
此算法是在拓扑排序的基础上进行的,首先对AOE图完成拓扑排序,并将排序结果保存在栈中(代码中是栈stack2)。
在拓扑排序完成之后,计算各个事件(顶点)、活动(弧)的etv、ltv、ete、lte的值。
其中lte = ete的活动(弧)即为关键活动,关键活动连接成为关键路径。
代码如下:(有详细注释)(较容易理解)
/* 弧 edge */
typedef struct EdgeNode
{
int adjvex; // 弧指向的顶点位置(下标)
struct EdgeNode *next;
}EdgeNode;
/* 顶点 node */
typedef struct VertexNode
{
int in; // 顶点入度
int data;
EdgeNode *firstedge; // 第1个依附于该顶点的弧
}VertexNode, AdjList[MAXVEX];
/* 邻接表 */
typedef struct
{
AdjList adjList;
int numVertexes;
int numEdges;
}graphAdjList, *GraphAdjList;
int *etv, *ltv;
int *stack2; // 用于存储拓扑序列的栈
int top2; // stack2的栈顶指针
/* 拓扑排序算法 */
/* 若GL无回路 则输出拓扑排序序列并返回OK 否则返回ERROR */
/* 拓扑排序仅改变这些顶点的排列顺序(这里是按顺序存放在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 )
{
/* 将入度为0的顶点下标入栈 */
stack[++top] = i;
}
}
top2 = 0;
etv = (int *)malloc(GL->numVertexes*sizeof(int));
/* 初始化etv都为0 */
for( i=0; i < GL->numVertexes; i++ )
{
etv[i] = 0;
}
stack2 = (int *)malloc(GL->numVertexes*sizeof(int));
/* 拓扑排序 */
while( 0 != top )
{
/* 出栈 */
gettop = stack[top--];
/* 原本拓扑排序这里需要打印 */
// printf("%d -> ", GL->adjList[gettop].data);
/* 保存拓扑序列顺序 */
stack2[++top2] = gettop;
/* 记录拓扑排序输出顶点个数(用以判断是否存在环路) */
count++;
/* 取出一个入度为0的顶点后 以它为前驱的顶点入度-1 */
for( e=GL->adjList[gettop].firstedge; e; e=e->next )
{
k = e->adjvex;
/* 入度-1后若为0 则入栈 */
if( !(--GL->adjList[k].in) )
{
stack[++top] = k;
}
/* 如果某一个顶点有多个入度 则该事件需要等待这几个活动都完成才能有效 */
/* 故这里判断如果有更长的路径 则替代原本的etv */
if( (etv[gettop]+e->weight) > etv[k] )
{
etv[k] = etv[gettop] + e->weight;
}
}
}
/* 如果count小于顶点数 说明存在环 */
if( count < GL->numVertexes )
{
return ERROR;
}
else
{
return OK;
}
}
/* 求关键路径 */
/* GL为有向图 输出GL的各项关键活动 */
void CriticalPath(GraphAdjList GL)
{
EdgeNode *e;
int i, gettop, k, j;
int ete, lte;
/* 调用改进后的拓扑排序 */
/* 求出etv数组(事件最早发生时间)和stack2(拓扑序列) */
TopologicalSort(GL);
/* 初始化ltv(事件最晚发生时间)都为汇点的时间(ltv是由汇点开始倒推的) */
ltv = (int *)malloc(GL->numVertexes*sizeof(int));
for( i=0; i < GL->numVertexes; i++ )
{
/* 汇点的发生时间(汇点etv=ltv) */
ltv[i] = etv[GL->numVertexes-1];
}
/* 从汇点倒推计算ltv */
while( 0 != top2 )
{
/* 第一个出栈的是汇点 */
gettop = stack2[top2--];
/* 汇点无firstedge 故直接出栈下一个点 */
/* 只有有出度的顶点 firstedge才不为空 */
for( e=GL->adjList[gettop].firstedge; e; e=e->next )
{
k = e->adjvex;
/* 如果某一个顶点有多个出度 则在计算ltv时需要选择通过这几条出度算得的ltv的最小值 */
/* 为了因为如果没有选择其中的最小值 则计算出更小的ltv的那条出度的路径将导致汇点延时 */
/* 故这里判断如果有更小的ltv结果 则替代原本的ltv */
if( (ltv[k] - e->weight) < ltv[gettop] )
{
ltv[gettop] = ltv[k] - e->weight;
}
}
}
/* 通过etv和ltv 求ete和lte */
for( j=0; j < GL->numVertexes; j++ )
{
for( e=GL->adjList[j].firstedge; e; e=e->next )
{
k = e->adjvex;
/* 顶点(事件)的etv即它的出度的弧(活动)的ete */
ete = etv[j];
/* 弧(活动)的lte即它所指向的下一个顶点(事件)的ltv-弧的权值weight */
lte = ltv[k] - e->weight;
/* ete = lte的事件即为关键活动 连接成为关键路径 */
if( ete == lte )
{
/* 输出关键路径 */
printf("<v%d,v%d> length: %d , ", GL->adjList[j].data, GL->adjList[k].data, e->weight );
}
}
}
}
——cloud over sky
——2020/3/12
关键路径——CriticalPath算法的更多相关文章
- 关键路径(CriticalPath)算法
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #define MAXVEX 30 //最大顶 ...
- 算法与数据结构(八) AOV网的关键路径
上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...
- 算法与数据结构(八) AOV网的关键路径(Swift版)
上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...
- 图->有向无环图->求关键路径
文字描述 与AOV-网相对应的是AOE-网(Activity on Edge)即边表示活动的网.AOE-网是一个带权的有向无环图.其中,顶点表示事件Event,弧表示活动,权表示活动持续的时间.通常, ...
- AOE网与关键路径简介
前面我们说过的拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题.如果我们要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程, ...
- java数据结构_笔记(5)_图的算法
图的算法 1 图的遍历图的遍历就是从图中某个顶点出发,按某种方法对图中所有顶点访问且仅访问一次.遍历算法是求解图的连通性问题.拓扑排序和求关键路径等算法的基础. 2 深度优先遍历从图中某个顶点V 出发 ...
- MindFusion 中节点关键路径的遍历
工作中总能遇到 一些 奇葩的需求,提出这些奇葩需求的人,多半也是奇葩的人,要么不懂相关的计算机软件知识,要么就是瞎扯蛋,异想天开,然而这些奇葩的需求,我也总能碰到.言规正传,在一次项目中,使用了 Mi ...
- 数据结构(c语言版)代码
第1章 绪论 文档中源码及测试数据存放目录:数据结构\▲课本算法实现\▲01 绪论 概述 第一章作为绪论,主要介绍了数据结构与算法中的一些基本概念和术语.对于这些概念术语 ...
- 返回数据方法DeaCacheCommand,由CRL自动实现
越来越多的人学起了前端,或许部分的初衷仅是它简单易上手以及好找工作,毕竟几年前只会个html和css就能有工作,悄悄告诉泥萌,这也是博主一年前的初衷 还好numpy, scikit-learn都提供了 ...
随机推荐
- [转载] IE8+兼容小结
本文分享下我在项目中积累的IE8+兼容性问题的解决方法.根据我的实践经验,如果你在写HTML/CSS时候是按照W3C推荐的方式写的,然后下面的几点都关注过,那么基本上很大一部分IE8+兼容性问题都OK ...
- docker学习笔记一篇就通系列(持续更新)
docker三要素 仓库 镜像 容器 仓库 仓库用来存放docker的镜像,类似于github存放代码医养 镜像 镜像是一个模板,封装了应用程序和配置依赖的可交付的运行环境,这个打包好的运行环境就是镜 ...
- Educational Codeforces Round 77 (Rated for Div. 2) C. Infinite Fence
C. Infinite Fence 题目大意:给板子涂色,首先板子是顺序的,然后可以涂两种颜色,如果是r的倍数涂成红色,是b的倍数涂成蓝色, 连续的k个相同的颜色则不能完成任务,能完成任务则输出OBE ...
- SpringCloudGateWay学习 之 从函数式编程到lambda
文章目录 前言: 函数式编程: 什么是函数式编程: 函数式编程的特点 lambda表达式: 核心: 函数接口: 方法引用: 类型推断: 变量引用: 级联表达式跟柯里化: 前言: 这一系列的文章主要是为 ...
- C++中的隐式转换和explicit
隐式转换 c++中的数据类型转换分为隐式转换和显示转换: 显示转换即使用static_cast等方法进行转换,相关内容请参考 <C++数据类型转换>: 隐式转换则是编译器完成的,如,boo ...
- Spark离线日志分析,连接Spark出现报错
首先,我的代码是这样的 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object ...
- case when的使用-解决分表查数据给某一个字段
一个表中存的是目前有效的菜单,另外一个表中存的是有效菜单的历史更改数据 需要查询历史数据的时候,带上访问的历史数据菜单名称 SELECT msg.msg_id, msg.from_user_name, ...
- Universalimageloader 原图片大小获取
Universalimageloader1.9.5上还没有对外提供获取图片的原大小功能,如果需要获取图片的源大小,可参考stackoverflow上的解决办法 stackoverflow地址 主要实现 ...
- java套打
1:套打可能是以后软件开发可能会涉及到的功能,主要麻烦地方就是需要精确定位,光是打印发票还好,要是打印那种协议类型的特别麻烦,不仅长而且需要的数据多 ,定位麻烦. 2:而且大多数情况是需要去除页眉页脚 ...
- 当Tomcat遇上Netty
故事背景 嘀嘀嘀~,生产事故,内存泄漏! 昨天下午,突然收到运维的消息,分部某系统生产环境内存泄漏了,帮忙排查一下. 排查过程 第一步,要日志 分部给到的异常日志大概是这样(鉴于公司规定禁止截图禁止拍 ...