AOV网和AOE网

AOV网

顶点活动网络(Activity On Vertex, AOV):用顶点表示活动,边集表示活动优先关系的有向图。



上图中,结点表示课程,有向边表示课程的先导关系。

显然,图中不应该出现有向环,否则会让优先关系出现逻辑错误。

AOE网

定义

边活动网络(Acitivity On Edge, AOE):用带权的边集表示活动,用顶点表示事件的有向图。AOE比AOV包含更多信息。



上图中,a1-a6表示活动,即要学习的课程;边权表示学习时间;结点表示事件,如v2表示空间解析几何已经学完,可以开始学习复变函数,v5表示泛函分析的先导课程已经学完,接下来可以开始学习泛函分析。

所以: “事件” 仅表示一个 中介状态

显然,图中不应该出现有向环,否则会让优先关系出现逻辑错误。

与AOV网的转化

  • AOV网中的顶点:拆分为两个顶点,分别表示活动的起点和终点(在AOE网中就是两个事件),而两个顶点间用有向边连接,该有向边就表示AOV网中原顶点的活动,最后在赋予边权。

  • AOV网中的边:原AOV网中的边全部视为空活动,边权为0。

AOE网中着重解决的两个问题

AOE网是基于工程提出的概念,它着重解决两个问题

1.最长路径问题

工程起始到终止至少需要多少时间 取决于AOE网中的最长路径

如何理解此处的“至少”和“最长”? 设想你有一排手办,而你要定制一个展示盒把所有的手办全部装在里面展示,那么,你所有定制的展示盒的最低高度显然就取决于你那一排手办里最高高度的手办。也可以从反面理解,木桶平放时所能装下的最高水位取决于木桶的最低板长

2.关键活动问题

哪条(些)路径上的活动是影响整个工程进度的关键:显然最长路径上的活动是影响整个工程进度的关键,如果缩短了最长路径上活动的时间,就能缩短工程的总体时间,反之,就会延长。比如,如果一排手办中最高的手办的高度由1m变成0.5m,那么所要定制的展示盒就只需要0.5m高而不在是1m高。

总结

所以:我们称最长路径为“关键路径”,称最长路径上的活动为“关键活动”。它们是影响工程时间的关键。

如果我们求出了关键路径,就能求出工程最短时间。

而需要求出工程最短时间,必然要借助关键路径。

最长路径

无正环的图

如果一个图中没有正环(指从原点可达的正环),那么只需要把边权乘以-1,令其变成相反数,就可以将最长路径问题转化为最短路径问题,使用Bellman-Ford或SPFA解决,注意不能使用Dijstra(无法处理负权边)。

最短路径问题介绍:<数据结构>图的最短路径问题

有向无环图的最短路径

见下文“关键路径算法

其他情况

为NP-Hard问题(无法用 多项式时间复杂度的算法解决 的问题)。

关键路径算法:确定关键活动,求出工程最短时间

前置定义



e[a]: 活动a的最早发生时间

l[a]: 活动a的最晚发生时间

若 "e[a] == l[a]" 说明活动a不能拖延,活动a是关键活动。

ve[i]: 事件i的最早发生时间

le[i]: 事件i的最晚发生时间

求解e[a]、l[a]转化为求解ve[i]、le[i]这两个新数组

  1. 对于活动ar来说,只要在事件Vi发生是马上开始,就可以使得活动ar开始的时间最早,因此e[r] = ve[i]
  2. 如果l[r]是活动ar的最迟发生时间,那么l[r] + length[r]就是事件Vj的最迟发生时间(length[r]表示活动a的边权,即活动a持续时间)。因此l[r] = vl[j] - length[r]。

下面讨论如何求解ve[ ]与vl[ ]

ve数组求解

数学分析

有k个事件 Vi1 ~ Vik,通过相应的活动ar1 ~ ark, 到达事件Vj。(如下图)

活动的边权分别为length[r1] ~ length[rk]。

假设 Vi1 ~ Vik 时间的最早发生时间 ve[i1] ~ ve[ik] 以及 length[r1] ~ length[rk] 均已知, 则 事件Vj发生的最早时间就是ve[i1]+length[r1] ~ ve[ik]+length[rk] 中的 最大值

此处时间节点Ve[]取最大值 是因为只有取最大值才能保证,在Vj开始时,Vj所有先导事件都已完成。

代码实现:拓扑排序

拓扑排序介绍:<数据结构>拓扑排序

  1. 根据上文分析,要想知道ve[j],那么ve[i1]~ve[ik]必须得到。 即在访问某个结点时保证它的前驱结点都已经被访问过 ————> 拓扑排序

  2. 在拓扑排序中无法根据当前结点得到它的前驱结点————>访问到某个结点Vi时,不去寻找它的前驱结点,而是用它更新所有后继结点的ve[]值,这样,在访问它的后继结点时,后继结点的ve值必然是已经被更新过的。

stack<int> topOrder; //拓扑序列,为后面的逆拓扑排序做准备
//拓扑排序,顺便求ve数组。 ve数组初始化为0
bool topologicalSort(){
queue<int> q;
for(int i = 0; i<n; i++){
if(inDegree[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front();
q.pop();
topOrder.push(u); //将u加入拓扑序列
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].v; //u的i号后继结点的编号为v
inDegree[v]--;
if(inDegree[v] == 0){
q.push(v);
}
//用u来更新u的所有后继结点v
if(ve[u]+G[u][i].w > ve[v]){
ve[v] = ve[u] + G[u][i].w;
}
}
}
if(topOrder.size() == n) return true;
else return false;
}

vl数组求解

数学分析

从事件Vi出发,通过相应的活动 ar1 ~ ark 可以到达k个事件 Vj1 ~ Vjk ,活动的边权为 length[rl] ~ length[rk]。

假设 Vj1 ~ Vjk 时间的最迟发生时间 vl[j1] ~ vl[jk] 以及 length[r1] ~ length[rk] 均已知, 则 事件Vi发生的最迟时间就是vl[j1]-length[r1] ~ vl[jk]-length[rk] 中的 最小值

此处时间节点Vl[ik]取最小值 是因为只有取最小值才能保证,在Vi开始时,Vi所有后继事件都能(在其最晚时间节点前)完成。

代码实现:逆拓扑排序

算出vl[i]需保证i的后继结点的最迟时间即 vl[j1] ~ vl[jk] 都已被算出。

与求ve数组的过程反向,即将拓扑排序序列逆序访问,同时更新vl值即可。

fill(vl,vl+n, ve[n-1]);  //vl数组初始化,初始值为汇点的ve值

    //直接使用topOrder出栈即为逆拓扑序列,求解vl数组
while(!topOrder.empty()){
int u = topOrder.top(); //栈顶元素为u
topOrder.pop();
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].v; //u的后继结点v
//用u的所有后继结点v的vl值来更新vl[u]
if(vl[v] - G[u][i].w < vl[u]){
vl[u] = vl[u] - G[u][i].w;
}
}
}

关键路径算法实现

基本步骤

先求点,再夹边:按下面三个步骤进行

主体代码

适用于汇点唯一且确定 的情况,以n-1号顶点为汇点为例。

#include<stdio.h>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
const int MAXV = 100;
struct Node{
int v, w;
}; int ve[MAXV];
int vl[MAXV];
int n; //顶点数
int inDegree[MAXV]; //储存结点入度,在主函数中初始化
vector<Node> G[MAXV]; //邻接表表示图G
stack<int> topOrder; //拓扑序列,为后面的逆拓扑排序做准备 bool topologicalSort(); //拓扑排序,计算ve数组(对应步骤1)
int CirticalPath(); //逆拓扑排序 与 输出关键活动和换件路径长度(对应步骤2、3) //关键路径,不是有向无环图返回-1,否则返回关键路径长度
int CirticalPath(){
fill(ve, ve+n, 0); //ve数组初始化为0,则汇点的ve值(0+关键路径长度)就等于关键路径长度。
if(topologicalSort() == false){ //调用拓扑排序函数,计算ve数组
return -1; //不是有向无环图,返回-1
}
//此时的ve数组以经过拓扑排序已更新赋值,ve[n-1]为拓扑排序终点(即有向图汇点)的ve值
fill(vl,vl+n, ve[n-1]); //vl数组初始化,初始值为汇点的ve值 //直接使用topOrder出栈即为 逆拓扑序列 ,求解vl数组
while(!topOrder.empty()){
int u = topOrder.top(); //栈顶元素为u
topOrder.pop();
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].v; //u的后继结点v
//用u的所有后继结点v的vl值来更新vl[u]
if(vl[v] - G[u][i].w < vl[u]){
vl[u] = vl[u] - G[u][i].w;
}
}
} //遍历邻接表的所有边,计算活动的最早开始时间e和最迟开始时间l
for(int u = 0; u < n; u++){
for(int i = 0; i < G[u].size(); i++){
int v = G[u][v].v, w = G[u][v].w;
//活动的最早开始时间e和最迟开始时间l
int e = ve[u], l = vl[v] - G[u][v].w;
//如果e==l,说明活动u->v是关键路径
if(e == l){
printf("%d->%d\n", u, v);
}
}
} return ve[n-1]; // 返回关键路径长度
} //拓扑排序,顺便求ve数组,ve数组初始化为0
bool topologicalSort(){
queue<int> q;
for(int i = 0; i<n; i++){
if(inDegree[i] == 0){
q.push(i);
}
}
while(!q.empty()){
int u = q.front();
q.pop();
topOrder.push(u); //将u加入拓扑序列
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].v; //u的i号后继结点的编号为v
inDegree[v]--;
if(inDegree[v] == 0){
q.push(v);
}
//用u来更新u的所有后继结点v
if(ve[u]+G[u][i].w > ve[v]){
ve[v] = ve[u] + G[u][i].w;
}
}
}
if(topOrder.size() == n) return true; //无环,可计算关键路径
else return false; //图中有环,无法计算关键路径
}

几个注意点

  1. 图采用邻接表示实现:<数据结构>图的构建与基本遍历方法
  2. e和l只是用来判断关键活动,并输出,不必保存。如果需要保存可在结构体Node中添加域e和l。
  3. 范围拓展:

    a.汇点不唯一:引入超级汇点,将所有汇点指向超级汇点,再调用函数CritialPath()

    b.汇点不确定:寻找汇点,汇点的ve值必然最大,故可在 fill函数之前找到ve数组最大值即可
    fill(vl, vl+n, v[n-1])

    替换为:

    int maxLength = 0;
    for(int i = 0; i < n; i++){
    if(ve[i] > manLength){
    maxLength = ve[i];
    }
    }

<数据结构>关键路径的更多相关文章

  1. 算法与数据结构(八) AOV网的关键路径

    上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...

  2. 数据结构:关键路径,利用DFS遍历每一条关键路径JAVA语言实现

    这是我们学校做的数据结构课设,要求分别输出关键路径,我查遍资料java版的只能找到关键路径,但是无法分别输出关键路径 c++有可以分别输出的,所以在明白思想后自己写了一个java版的 函数带有输入函数 ...

  3. 算法与数据结构(八) AOV网的关键路径(Swift版)

    上篇博客我们介绍了AOV网的拓扑序列,请参考<数据结构(七) AOV网的拓扑排序(Swift面向对象版)>.拓扑序列中包括项目的每个结点,沿着拓扑序列将项目进行下去是肯定可以将项目完成的, ...

  4. 数据结构期末复习( はち)--VOA图关键路径求法

    题目如下图: 注:将123456当成abcdef. 事件最早发生事件求法:找从原点到该事件的最长路径(从前往后推) 对a:Ve=0 对b:Ve=max{ 2 , 15+4 }=19 对c:Ve=15 ...

  5. 数据结构实验之图论十一:AOE网上的关键路径【Bellman_Ford算法】

    Problem Description 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图.     AOE(Activity On Edge)网:顾名思义,用边 ...

  6. 关键路径(AOE)---《数据结构》严蔚敏

    // exam1.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #include &l ...

  7. SDUTOJ 2498 数据结构实验之图论十一:AOE网上的关键路径

    题目链接:http://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/2498.html 题目大意 略. 分析 ...

  8. java数据结构_笔记(4)_图

    图一.概念.图: 是一种复杂的非线性数据结构.图的二元组定义: 图 G 由两个集合 V 和 E 组成,记为:G=(V, E)  其中: V 是顶点的有穷非空集合,E 是 V 中顶点偶对(称为边)的有穷 ...

  9. C++数据结构之图

    图的实现是一件很麻烦的事情,很多同学可能在学数据结构时只是理解了图的基本操作和遍历原理,但并没有动手实践过.在此,我说说我的实现过程. 首先,在草稿纸上画一个图表,这里是有向图,无向图也一样,如下: ...

随机推荐

  1. Flink(二)【架构原理,组件,提交流程】

    目录 一.运行架构 1.架构 2.组件 二.核心概念 TaskManager . Slots Parallelism(并行度) Task .Subtask Operator Chains(任务链) E ...

  2. win10产品密钥 win10永久激活密钥(可激活win10所有版本)

    https://www.win7w.com/win10jihuo/18178.html#download 很多人都在找2019最新win10永久激活码,其实win10激活码不管版本新旧都是通用的,也就 ...

  3. C++自定义字符串类

    //header.h #ifndef _HEADER_H #define _HEADER_H #define defaultSize 128 #include<iostream> #inc ...

  4. 2.7 Rust Structs

    A struct, or structure, is a custom data type that lets you name and package together multiple relat ...

  5. 【Spring Framework】Spring入门教程(二)基于xml配置对象容器

    基于xml配置对象容器--xml 标签说明 alias标签 作用:为已配置的bean设置别名 --applicationContext.xml配置文件 <?xml version="1 ...

  6. 最基础前端路由实现,事件popstate使用

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. C#.NET编程小考30题错题纠错

    1)以下关于序列化和反序列化的描述错误的是( C). a) 序列化是将对象的状态存储到特定存储介质中的过程 b) 二进制格式化器的Serialize()和Deserialize()方法可以分别用来实现 ...

  8. Mysql配置文件 客户端

    [client] #默认链接的端口 port=3306 #默认链接的socket的位置 socket=/var/lib/mysql.sock #默认编码格式 default-character-set ...

  9. LeetCode 36. Valid Sudoku (Medium)

    题目 Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according ...

  10. Mybatis环境搭建及测试

    1.新建java project,导入相应jar包 本次使用到的mybatis-3.2.7版本 mybatis需要jar包:mybatis-3.2.7.jar.lib文件下的依赖jar mysql驱动 ...