基于AOE网的关键路径的求解
【1】关键路径
在我的经验意识深处,“关键”二字一般都是指临界点。
凡事万物都遵循一个度的问题,那么存在度就会自然有临界点。
关键路径也正是研究这个临界点的问题。
在学习关键路径前,先了解一个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 为止,做法完全相同。
最终关键路径如下图所示:
注意:本例是唯一一条关键路径,并不等于不存在多条关键路径。
如果是多条关键路径,则单是提高一条关键路径上的关键活动速度并不是能导致整个工程缩短工期、
而必须提高同时在几条关键路径上的活动的速度。
【3】关键路径是代码实现
本示例代码与算法有些不同,但是效果相同,都是为了达到一个共同目的:理解并学习关键路径算法。
#include <iostream>
#include "Stack.h"
#include <malloc.h>
using namespace std; #define MAXVEX 10
#define MAXEDGE 13 // 全局栈
SeqStack<int> sQ2; typedef struct EdgeNode
{
int adjvex; // 邻接点域,存储该顶点对应的下标
int weight; // 边的权值
struct EdgeNode* next; // 链域
} EdgeNode; typedef struct VertexNode
{
int inNum; // 顶点入度值
int data; // 顶点数值欲
EdgeNode* firstedge; // 边表头指针
} VertexNode, AdjList[MAXVEX]; typedef struct
{
AdjList adjList;
int numVertexes, numEdges; // 图中当前顶点数和边数(对于本案例,已经存在宏定义)
} graphAdjList, *GraphAdjList; // 构建节点
EdgeNode* BuyNode()
{
EdgeNode* p = (EdgeNode*)malloc(sizeof(EdgeNode));
p->adjvex = -;
p->next = NULL;
return p;
}
// 初始化图
void InitGraph(graphAdjList& g)
{
for (int i = ; i < MAXVEX; ++i)
{
g.adjList[i].firstedge = NULL;
}
}
// 创建图
void CreateGraph(graphAdjList& g)
{
int i = , begin = , end = , weight = ;
EdgeNode *pNode = NULL;
cout << "输入10个顶点信息(顶点 入度):" << endl;
for (i = ; i < MAXVEX; ++i)
{
cin >> g.adjList[i].data >> g.adjList[i].inNum;
}
cout << "输入13条弧的信息(起点 终点 权值):" << endl;
for (i = ; i < MAXEDGE; ++i)
{
cin >> begin >> end >> weight;
pNode = BuyNode();
pNode->adjvex = end;
pNode->weight = weight;
pNode->next = g.adjList[begin].firstedge;
g.adjList[begin].firstedge = pNode;
}
}
// 打印输入信息的逻辑图
void PrintGraph(graphAdjList &g)
{
cout << "打印AOE网的邻接表逻辑图:" << endl;
for (int i = ; i < MAXVEX; ++i)
{
cout << " " << g.adjList[i].inNum << " " << g.adjList[i].data << " ";
EdgeNode* p = g.adjList[i].firstedge;
cout << "-->";
while (p != NULL)
{
int index = p->adjvex;
cout << "[" << g.adjList[index].data << " " << p->weight << "] " ;
p = p->next;
}
cout << endl;
}
}
// 求拓扑序列
bool TopologicalSort(graphAdjList g, int* pEtv)
{
EdgeNode* pNode = NULL;
int i = , k = , gettop = ;
int nCnt = ;
SeqStack<int> sQ1;
for (i = ; i < MAXVEX; ++i)
{
if ( == g.adjList[i].inNum)
sQ1.Push(i);
}
for (i = ; i < MAXVEX; ++i)
{
pEtv[i] = ;
}
while (!sQ1.IsEmpty())
{
sQ1.Pop(gettop);
++nCnt;
sQ2.Push(gettop); // 将弹出的顶点序号压入拓扑序列的栈
if (MAXVEX == nCnt)
{ //去掉拓扑路径后面的-->
cout << g.adjList[gettop].data << endl;
break;
}
cout << g.adjList[gettop].data << "-->";
pNode = g.adjList[gettop].firstedge;
while (pNode != NULL)
{
k = pNode->adjvex;
--g.adjList[k].inNum;
if ( == g.adjList[k].inNum)
sQ1.Push(k);
if (pEtv[gettop] + pNode->weight > pEtv[k])
pEtv[k] = pEtv[gettop] + pNode->weight;
pNode = pNode->next;
}
}
return nCnt != MAXVEX;
}
// 关键路径
void CriticalPath(graphAdjList g, int* pEtv, int* pLtv)
{
// pEtv 事件最早发生时间
// PLtv 事件最迟发生时间
EdgeNode* pNode = NULL;
int i = , gettop = , k =, j = ;
int ete = , lte = ; // 声明活动最早发生时间和最迟发生时间变量
for (i = ; i < MAXVEX; ++i)
{
pLtv[i] = pEtv[MAXVEX-]; // 初始化
}
while (!sQ2.IsEmpty())
{
sQ2.Pop(gettop); // 将拓扑序列出栈,后进先出
pNode = g.adjList[gettop].firstedge;
while (pNode != NULL)
{ // 求各顶点事件的最迟发生时间pLtv值
k = pNode->adjvex;
if (pLtv[k] - pNode->weight < pLtv[gettop])
pLtv[gettop] = pLtv[k] - pNode->weight;
pNode = pNode->next;
}
}
// 求 ete, lte, 和 关键路径
for (j = ; j < MAXVEX; ++j)
{
pNode = g.adjList[j].firstedge;
while (pNode != NULL)
{
k = pNode->adjvex;
ete = pEtv[j]; // 活动最早发生时间
lte = pLtv[k] - pNode->weight; // 活动最迟发生时间
if (ete == lte)
cout << "<V" << g.adjList[j].data << ",V" << g.adjList[k].data << "> :" << pNode->weight << endl;
pNode = pNode->next;
}
}
}
void main()
{
graphAdjList myg;
InitGraph(myg);
cout << "创建图:" << endl;
CreateGraph(myg);
cout << "打印图的邻接表逻辑结构:" << endl;
PrintGraph(myg); int* pEtv = new int[MAXVEX];
int* pLtv = new int[MAXVEX]; cout << "求拓扑序列(全局栈sQ2的值):" << endl;
TopologicalSort(myg, pEtv);
cout << "打印数组pEtv(各个事件的最早发生时间):" << endl;
for(int i = ; i < MAXVEX; ++i)
{
cout << pEtv[i] << " ";
}
cout << endl << "关键路径:" << endl; CriticalPath(myg, pEtv, pLtv);
cout << endl;
}
/*
创建图:
输入10个顶点信息(顶点 入度):
0 0
1 1
2 1
3 2
4 2
5 1
6 1
7 2
8 1
9 2
输入13条弧的信息(起点 终点 权值):
0 1 3
0 2 4
1 3 5
1 4 6
2 3 8
2 5 7
3 4 3
4 6 9
4 7 4
5 7 6
6 9 2
7 8 5
8 9 3
打印图的邻接表逻辑结构:
打印AOE网的邻接表逻辑图:
0 0 -->[2 4] [1 3]
1 1 -->[4 6] [3 5]
1 2 -->[5 7] [3 8]
2 3 -->[4 3]
2 4 -->[7 4] [6 9]
1 5 -->[7 6]
1 6 -->[9 2]
2 7 -->[8 5]
1 8 -->[9 3]
2 9 -->
求拓扑序列(全局栈sQ2的值):
0-->1-->2-->3-->4-->6-->5-->7-->8-->9
打印数组pEtv(各个事件的最早发生时间):
0 3 4 12 15 11 24 19 24 27
关键路径:
<V0,V2> :4
<V2,V3> :8
<V3,V4> :3
<V4,V7> :4
<V7,V8> :5
<V8,V9> :3
*/
// Stack.h
// 顺序栈的实现
#pragma once #include <assert.h>
#include <string.h>
#include <iostream>
#define STACKSIZE 100
using namespace std;
template<class Type>
class SeqStack
{
private:
Type *data;
int top;
int size;
public:
SeqStack(int sz = STACKSIZE);
~SeqStack();
SeqStack(const SeqStack<Type> &st);
SeqStack<Type> operator=(const SeqStack<Type> &st);
bool Push(const Type &item);
bool Pop(Type &item);
bool GetTop(Type &item);
bool IsEmpty() const;
bool IsFull() const;
void MakeEmpty();
int StackLen();
void PrintStack();
}; template<class Type>
SeqStack<Type>::SeqStack(int sz)
{
size = sz > STACKSIZE ? sz : STACKSIZE;
data = new Type[size];
assert(data!=NULL);
for (int i = ; i < size; i++)
{
data[i] = NULL;
}
top = -;
}
template<class Type>
SeqStack<Type>::~SeqStack()
{
if (data != NULL)
{
delete []data;
data = NULL;
}
size = ;
top = -;
}
template<class Type>
SeqStack<Type>::SeqStack(const SeqStack<Type> &st)
{
size = st.size;
data = new Type[size];
assert(data != NULL);
memcpy(data, st.data, (st.top + )*sizeof(Type));
top = st.top;
}
template<class Type>
SeqStack<Type> SeqStack<Type>::operator=(const SeqStack<Type> &st)
{
if (this != &st)
{
delete []data;
size = st.size;
data = new Type[size];
assert(data != NULL);
memcpy(data, st.data, (st.top + )*sizeof(Type));
top = st.top;
}
return *this;
}
template<class Type>
bool SeqStack<Type>::Push(const Type &item)
{
if (top < size-)
{
data[++top] = item;
return true;
}
else
{
return false;
}
}
template<class Type>
bool SeqStack<Type>::Pop(Type &item)
{
if (top >= )
{
item = data[top--];
return true;
}
else
{
return false;
}
}
template<class Type>
bool SeqStack<Type>::GetTop(Type &item)
{
if (top >= )
{
item = data[top];
return true;
}
return false;
}
template<class Type>
bool SeqStack<Type>::IsEmpty() const
{
return - == top;
}
template<class Type>
bool SeqStack<Type>::IsFull() const
{
return top >= size - ;
}
template<class Type>
void SeqStack<Type>::MakeEmpty()
{
top = -;
}
template<class Type>
int SeqStack<Type>::StackLen()
{
return top + ;
}
template<class Type>
void SeqStack<Type>::PrintStack()
{
for (int i = top; i >= ; i--)
{
cout << data[i] << " ";
}
cout << endl;
}
基于AOE网的关键路径的求解的更多相关文章
- AOE网与关键路径简介
前面我们说过的拓扑排序主要是为解决一个工程能否顺序进行的问题,但有时我们还需要解决工程完成需要的最短时间问题.如果我们要对一个流程图获得最短时间,就必须要分析它们的拓扑关系,并且找到当中最关键的流程, ...
- AOE网与关键路径
声明:图片及内容基于https://www.bilibili.com/video/BV1BZ4y1T7Yx?from=articleDetail 原理 AOE网 关键路径 数据结构 核心代码 Topo ...
- _DataStructure_C_Impl:AOE网的关键路径
//_DataStructure_C_Impl:CriticalPath #include<stdio.h> #include<stdlib.h> #include<st ...
- AOE网的关键路径的计算
求关键路径,只需理解顶点(事件)和边(活动)各自的两个特征属性以及求法即可: Ø 先根据首结点的Ve(j)=0由前向后(正拓扑序列)计算各顶点的最早发生时间 Ø 再根据终结点的Vl(j)等于它的V ...
- 教你轻松计算AOE网关键路径(转)
原文链接:http://blog.csdn.net/wang379275614/article/details/13990163 本次结合系统分析师-运筹方法-网络规划技术-关键路径章节,对原文链接描 ...
- 教你轻松计算AOE网关键路径
认识AOE网 有向图中,用顶点表示活动,用有向边表示活动之间开始的先后顺序,则称这种有向图为AOV网络:AOV网络可以反应任务完成的先后顺序(拓扑排序). 在AOV网的边上加上权值表示完成该活动所需的 ...
- AOE网络的关键路径问题
关于AOE网络的基本概念可以参考<数据结构>或者search一下就能找到,这里不做赘述. 寻找AOE网络的关键路径目的是:发现该活动网络中能够缩短工程时长的活动,缩短这些活动的时长,就可以 ...
- SDUT 2498 AOE网上的关键路径
AOE网上的关键路径 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 一个无环的有向图称为无 ...
- 数据结构关于AOV与AOE网的区别
AOV网,顶点表示活动,弧表示活动间的优先关系的有向图. 即如果a->b,那么a是b的先决条件. AOE网,边表示活动,是一个带权的有向无环图, 其中顶点表示事件,弧表示活动,权表示活动持续时间 ...
随机推荐
- VS2008 C++ 利用WinHttp API获取Http请求/响应头部Header
http://www.cnblogs.com/LCCRNblog/p/3833472.html 这一篇博客中,实现了获取http请求/响应后的html源码,现在需要获取http请求/响应的头部Head ...
- Problem P
题意:FJ养牛,他想知道中间的牛是那一头: 思路:这道题有点水,思路就不写了 #include #include #include #define maxn 10005 using namespace ...
- LCIS(区间合并)
LCIS Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submissi ...
- Good Luck in CET-4 Everybody!
Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Ja ...
- 使用Lock锁生产者消费者模式
package com.java.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurren ...
- [转]彻底弄懂css中单位px和em,rem的区别
难怪会问我 rem 和 em, 这俩或在移动端还是很有必要学习的. root em OK? 国内的设计师大都喜欢用px,而国外的网站大都喜欢用em和rem,那么三者有什么区别,又各自有什么优劣呢? P ...
- ldap数据库--ODSEE--安装
在安装之前最好查看一下服务器硬件是否满足要求,是否需要更改一些系统配置来达到使用ldap数据库的最有性能.实际使用的ldap数据库是oracle的产品,DS70即ODSEE. 安装环境:solaris ...
- Gmail,QMail,163邮箱的 IMAP/SMTP/POP3 地址
我们在客户端设置邮箱或者使用 PHPMailer 发送邮件的时候,我们都会去查找这些邮箱的 IMAP/SMTP/POP3 地址,这里就列出 Gmail, QMail, 163邮箱这三个常用邮箱的这些地 ...
- Android Oreo 8.0 新特性实战 Autosizing TextView --自动缩放TextView
Android Oreo 8.0 新特性实战 Autosizing TextView --自动缩放TextView 8.0出来很久了,这个新特性已经用了很久了,但是一直没有亲自去试试.这几天新的需求来 ...
- bootstrap导航栏.nav与.navbar区别
刚刚看了bootstrap的导航栏,发现有点弄混了,现在来整理一下: 一.简单的ul,li组成的导航: <ul class="nav nav-pills justify-content ...