基于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】关键路径是代码实现

本示例代码与算法有些不同,但是效果相同,都是为了达到一个共同目的:理解并学习关键路径算法。

  1 #include <iostream>
  2 #include "Stack.h"
  3 #include <malloc.h>
  4 using namespace std;
  5
  6 #define  MAXVEX   10
  7 #define  MAXEDGE  13
  8
  9 // 全局栈
 10 SeqStack<int> sQ2;
 11
 12 typedef struct EdgeNode
 13 {
 14     int adjvex;    // 邻接点域,存储该顶点对应的下标
 15     int weight; // 边的权值
 16     struct EdgeNode* next; // 链域
 17 } EdgeNode;
 18
 19 typedef struct VertexNode
 20 {
 21     int inNum;    // 顶点入度值
 22     int data;    // 顶点数值欲
 23     EdgeNode* firstedge; // 边表头指针
 24 } VertexNode, AdjList[MAXVEX];
 25
 26 typedef struct
 27 {
 28     AdjList adjList;
 29     int numVertexes, numEdges; // 图中当前顶点数和边数(对于本案例,已经存在宏定义)
 30 } graphAdjList, *GraphAdjList;
 31
 32 // 构建节点
 33 EdgeNode* BuyNode()
 34 {
 35     EdgeNode* p = (EdgeNode*)malloc(sizeof(EdgeNode));
 36     p->adjvex = -1;
 37     p->next = NULL;
 38     return p;
 39 }
 40 // 初始化图
 41 void InitGraph(graphAdjList& g)
 42 {
 43     for (int i = 0; i < MAXVEX; ++i)
 44     {
 45         g.adjList[i].firstedge = NULL;
 46     }
 47 }
 48 // 创建图
 49 void CreateGraph(graphAdjList& g)
 50 {
 51     int i = 0, begin = 0, end = 0, weight = 0;
 52     EdgeNode *pNode = NULL;
 53     cout << "输入10个顶点信息(顶点 入度):" << endl;
 54     for (i = 0; i < MAXVEX; ++i)
 55     {
 56         cin >> g.adjList[i].data >> g.adjList[i].inNum;
 57     }
 58     cout << "输入13条弧的信息(起点 终点 权值):" << endl;
 59     for (i = 0; i < MAXEDGE; ++i)
 60     {
 61         cin >> begin >> end >> weight;
 62         pNode = BuyNode();
 63         pNode->adjvex = end;
 64         pNode->weight = weight;
 65         pNode->next = g.adjList[begin].firstedge;
 66         g.adjList[begin].firstedge = pNode;
 67     }
 68 }
 69 // 打印输入信息的逻辑图
 70 void PrintGraph(graphAdjList &g)
 71 {
 72     cout << "打印AOE网的邻接表逻辑图:" << endl;
 73     for (int i = 0; i < MAXVEX; ++i)
 74     {
 75         cout << " " << g.adjList[i].inNum << " " << g.adjList[i].data << " ";
 76         EdgeNode* p = g.adjList[i].firstedge;
 77         cout << "-->";
 78         while (p != NULL)
 79         {
 80             int index = p->adjvex;
 81             cout << "[" << g.adjList[index].data << " " << p->weight << "] " ;
 82             p = p->next;
 83         }
 84         cout << endl;
 85     }
 86 }
 87 // 求拓扑序列
 88 bool TopologicalSort(graphAdjList g, int* pEtv)
 89 {
 90     EdgeNode* pNode = NULL;
 91     int i = 0, k = 0, gettop = 0;
 92     int nCnt = 0;
 93     SeqStack<int> sQ1;
 94     for (i = 0; i < MAXVEX; ++i)
 95     {
 96         if (0 == g.adjList[i].inNum)
 97             sQ1.Push(i);
 98     }
 99     for (i = 0; i < MAXVEX; ++i)
100     {
101         pEtv[i] = 0;
102     }
103     while (!sQ1.IsEmpty())
104     {
105         sQ1.Pop(gettop);
106         ++nCnt;
107         sQ2.Push(gettop); // 将弹出的顶点序号压入拓扑序列的栈
108         if (MAXVEX == nCnt)
109         {    //去掉拓扑路径后面的-->
110             cout << g.adjList[gettop].data << endl;
111             break;
112         }
113         cout << g.adjList[gettop].data << "-->";
114         pNode = g.adjList[gettop].firstedge;
115         while (pNode != NULL)
116         {
117             k = pNode->adjvex;
118             --g.adjList[k].inNum;
119             if (0 == g.adjList[k].inNum)
120                 sQ1.Push(k);
121             if (pEtv[gettop] + pNode->weight > pEtv[k])
122                 pEtv[k] = pEtv[gettop] + pNode->weight;
123             pNode = pNode->next;
124         }
125     }
126     return nCnt != MAXVEX;
127 }
128 // 关键路径
129 void CriticalPath(graphAdjList g, int* pEtv, int* pLtv)
130 {
131     // pEtv  事件最早发生时间
132     // PLtv  事件最迟发生时间
133     EdgeNode* pNode = NULL;
134     int i = 0, gettop = 0, k =0, j = 0;
135     int ete = 0, lte = 0; // 声明活动最早发生时间和最迟发生时间变量
136     for (i = 0; i < MAXVEX; ++i)
137     {
138         pLtv[i] = pEtv[MAXVEX-1]; // 初始化
139     }
140     while (!sQ2.IsEmpty())
141     {
142         sQ2.Pop(gettop); // 将拓扑序列出栈,后进先出
143         pNode = g.adjList[gettop].firstedge;
144         while (pNode != NULL)
145         {    // 求各顶点事件的最迟发生时间pLtv值
146             k = pNode->adjvex;
147             if (pLtv[k] - pNode->weight < pLtv[gettop])
148                 pLtv[gettop] = pLtv[k] - pNode->weight;
149             pNode = pNode->next;
150         }
151     }
152     // 求 ete, lte, 和 关键路径
153     for (j = 0; j < MAXVEX; ++j)
154     {
155         pNode = g.adjList[j].firstedge;
156         while (pNode != NULL)
157         {
158             k = pNode->adjvex;
159             ete = pEtv[j]; // 活动最早发生时间
160             lte = pLtv[k] - pNode->weight; // 活动最迟发生时间
161             if (ete == lte)
162                 cout << "<V" << g.adjList[j].data << ",V" << g.adjList[k].data << "> :" << pNode->weight << endl;
163             pNode = pNode->next;
164         }
165     }
166 }
167 void main()
168 {
169     graphAdjList myg;
170     InitGraph(myg);
171     cout << "创建图:" << endl;
172     CreateGraph(myg);
173     cout << "打印图的邻接表逻辑结构:" << endl;
174     PrintGraph(myg);
175
176     int* pEtv = new int[MAXVEX];
177     int* pLtv = new int[MAXVEX];
178
179     cout << "求拓扑序列(全局栈sQ2的值):" << endl;
180     TopologicalSort(myg, pEtv);
181     cout << "打印数组pEtv(各个事件的最早发生时间):" << endl;
182     for(int i = 0; i < MAXVEX; ++i)
183     {
184         cout << pEtv[i] << " ";
185     }
186     cout << endl << "关键路径:" << endl;
187
188     CriticalPath(myg, pEtv, pLtv);
189     cout << endl;
190 }
191 /*
192 创建图:
193 输入10个顶点信息(顶点 入度):
194 0 0
195 1 1
196 2 1
197 3 2
198 4 2
199 5 1
200 6 1
201 7 2
202 8 1
203 9 2
204 输入13条弧的信息(起点 终点 权值):
205 0 1 3
206 0 2 4
207 1 3 5
208 1 4 6
209 2 3 8
210 2 5 7
211 3 4 3
212 4 6 9
213 4 7 4
214 5 7 6
215 6 9 2
216 7 8 5
217 8 9 3
218 打印图的邻接表逻辑结构:
219 打印AOE网的邻接表逻辑图:
220 0 0 -->[2 4] [1 3]
221 1 1 -->[4 6] [3 5]
222 1 2 -->[5 7] [3 8]
223 2 3 -->[4 3]
224 2 4 -->[7 4] [6 9]
225 1 5 -->[7 6]
226 1 6 -->[9 2]
227 2 7 -->[8 5]
228 1 8 -->[9 3]
229 2 9 -->
230 求拓扑序列(全局栈sQ2的值):
231 0-->1-->2-->3-->4-->6-->5-->7-->8-->9
232 打印数组pEtv(各个事件的最早发生时间):
233 0 3 4 12 15 11 24 19 24 27
234 关键路径:
235 <V0,V2> :4
236 <V2,V3> :8
237 <V3,V4> :3
238 <V4,V7> :4
239 <V7,V8> :5
240 <V8,V9> :3
241  */
  1 // Stack.h
  2 // 顺序栈的实现
  3 #pragma  once
  4
  5 #include <assert.h>
  6 #include <string.h>
  7 #include <iostream>
  8 #define   STACKSIZE  100
  9 using namespace std;
 10 template<class Type>
 11 class   SeqStack
 12 {
 13     private:
 14         Type *data;
 15         int  top;
 16         int  size;
 17     public:
 18         SeqStack(int  sz = STACKSIZE);
 19         ~SeqStack();
 20         SeqStack(const SeqStack<Type> &st);
 21         SeqStack<Type> operator=(const SeqStack<Type> &st);
 22         bool Push(const Type &item);
 23         bool Pop(Type &item);
 24         bool GetTop(Type &item);
 25         bool IsEmpty() const;
 26         bool IsFull() const;
 27         void MakeEmpty();
 28         int  StackLen();
 29         void PrintStack();
 30 };
 31
 32 template<class Type>
 33 SeqStack<Type>::SeqStack(int sz)
 34 {
 35     size = sz > STACKSIZE ? sz : STACKSIZE;
 36     data = new Type[size];
 37     assert(data!=NULL);
 38     for (int i = 0; i < size; i++)
 39     {
 40         data[i] = NULL;
 41     }
 42     top = -1;
 43 }
 44 template<class Type>
 45 SeqStack<Type>::~SeqStack()
 46 {
 47     if (data != NULL)
 48     {
 49         delete []data;
 50         data = NULL;
 51     }
 52     size = 0;
 53     top = -1;
 54 }
 55 template<class Type>
 56 SeqStack<Type>::SeqStack(const SeqStack<Type> &st)
 57 {
 58     size = st.size;
 59     data = new Type[size];
 60     assert(data != NULL);
 61     memcpy(data, st.data, (st.top + 1)*sizeof(Type));
 62     top = st.top;
 63 }
 64 template<class Type>
 65 SeqStack<Type>  SeqStack<Type>::operator=(const SeqStack<Type> &st)
 66 {
 67     if (this != &st)
 68     {
 69         delete []data;
 70         size = st.size;
 71         data = new Type[size];
 72         assert(data != NULL);
 73         memcpy(data, st.data, (st.top + 1)*sizeof(Type));
 74         top = st.top;
 75     }
 76     return *this;
 77 }
 78 template<class Type>
 79 bool SeqStack<Type>::Push(const Type &item)
 80 {
 81     if (top < size-1)
 82     {
 83         data[++top] = item;
 84         return true;
 85     }
 86     else
 87     {
 88         return false;
 89     }
 90 }
 91 template<class Type>
 92 bool SeqStack<Type>::Pop(Type &item)
 93 {
 94     if (top >= 0)
 95     {
 96         item = data[top--];
 97         return true;
 98     }
 99     else
100     {
101         return false;
102     }
103 }
104 template<class Type>
105 bool SeqStack<Type>::GetTop(Type &item)
106 {
107     if (top >= 0)
108     {
109         item = data[top];
110         return true;
111     }
112     return false;
113 }
114 template<class Type>
115 bool SeqStack<Type>::IsEmpty() const
116 {
117     return -1 == top;
118 }
119 template<class Type>
120 bool SeqStack<Type>::IsFull() const
121 {
122     return top >= size - 1;
123 }
124 template<class Type>
125 void SeqStack<Type>::MakeEmpty()
126 {
127     top = -1;
128 }
129 template<class Type>
130 int SeqStack<Type>::StackLen()
131 {
132     return  top + 1;
133 }
134 template<class Type>
135 void SeqStack<Type>::PrintStack()
136 {
137     for (int i = top; i >= 0; i--)
138     {
139         cout << data[i] << "  ";
140     }
141     cout << endl;
142 }
时间: 2024-11-13 06:32:54

基于AOE网的关键路径的求解的相关文章

_DataStructure_C_Impl:AOE网的关键路径

//_DataStructure_C_Impl:CriticalPath #include<stdio.h> #include<stdlib.h> #include<string.h> #include"SeqStack.h" //图的邻接表类型定义 typedef char VertexType[4]; typedef int InfoPtr; //定义为整型,为了存放权值 typedef int VRType; #define MaxSize 5

教你轻松计算AOE网关键路径(转)

原文链接:http://blog.csdn.net/wang379275614/article/details/13990163 本次结合系统分析师-运筹方法-网络规划技术-关键路径章节,对原文链接描述不准确的地方做了修正. 认识AOE网 有向图中,用顶点表示活动,用有向边表示活动之间开始的先后顺序,则称这种有向图为AOV(Activity On Vertex)网络:AOV网络可以反应任务完成的先后顺序(拓扑排序). 在AOV网的边上加上权值表示完成该活动所需的时间,则称这样的AOV网为AOE(

SDUTOJ 2498 AOE网上的关键路径(最长路)

AOE网上的关键路径 Time Limit: 1000MS Memory limit: 65536K 题目描述 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图. AOE(Activity On Edge)网:顾名思义,用边表示活动的网,当然它也是DAG.与AOV不同,活动都表示在了边上,如下图所示: 如上所示,共有11项活动(11条边),9个事件(9个顶点).整个工程只有一个开始点和一个完成点.即只有一个入度为零的点(源点)和只有一个出度为零的点(汇点)

SDUT 2498 AOE网上的关键路径

AOE网上的关键路径 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Problem Description 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图.     AOE(Activity On Edge)网:顾名思义,用边表示活动的网,当然它也是DAG.与AOV不同,活动都表示在了边上,如下图所示:                                         如

AOE网络的关键路径问题

关于AOE网络的基本概念可以参考<数据结构>或者search一下就能找到,这里不做赘述. 寻找AOE网络的关键路径目的是:发现该活动网络中能够缩短工程时长的活动,缩短这些活动的时长,就可以缩短整个工程的时长.因此,寻找关键路径就是寻找关键活动. 接下来开始寻找一个工程中的关键路径(关键活动). 寻找关键路径,每本教材都会提及四个特征属性:Ve[],Vl[],e[],l[],此处可能还补充一个属性:活动ai的时间余量,也就是l[i]-e[i],当某个活动的时间余量=0时,该活动就是关键活动.所以

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

数据结构实验之图论十一:AOE网上的关键路径 Time Limit: 2000 ms Memory Limit: 65536 KiB Problem Description 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图.    AOE(Activity On Edge)网:顾名思义,用边表示活动的网,当然它也是DAG.与AOV不同,活动都表示在了边上,如下图所示:                                         如上所示

基于成熟网管平台的网管软件开发模式

随着计算机网络的迅速发展,特别是国际互联网的不断地推广,计算机网络的使用越来越广泛,人们的生产生活学习对计算机网络的依赖也越来越大.同时,随着计算机网络的网络规模的不断扩大和连入网络的设备越来越多样,网络的复杂性也越来越高,网络的异构性也越拉越高.于是,网络管理就成为了一个重要的研究课题. 网络管理是对硬件.软件.人力的综合使用和协调,对网络资源进行监视.测试.配置.分析.评价和控制,从而以合理的价格满足网络的需求,如实时运行性能.服务质量等.从定义中可以看出,网络管理包含了两个重要的任务,一是

基于Petri网的工作流分析和移植

基于Petri网的工作流分析和移植 一.前言 在实际应用场景,包括PEC的订单流程从下订单到订单派送一直到订单完成都是按照一系列预先规定好的工作流策略进行的. 通常情况下如果是采用面向过程的编程方法,我们采用的方式无非就是判断当前的工作流状态以及操作步骤来选择工作流分支继续下一步,如果整个工作流从起始到结束所执行的步骤不多的话,采用此方式相当简便,但如果步骤一多起来,或者分支太多以及需要判断的或者切换的状态太多的时候,很容易出错,或者说在原有的工作流分支上新增一个操作步骤,则改起代码来会非常繁琐

SDUTOJ 2498 AOE网上的关键路径 最短路spfa

Description 一个无环的有向图称为无环图(Directed Acyclic Graph),简称DAG图. AOE(Activity On Edge)网:顾名思义,用边表示活动的网,当然它也是DAG.与AOV不同,活动都表示在了边上,如下图所示: 如上所示,共有11项活动(11条边),9个事件(9个顶点).整个工程只有一个开始点和一个完成点.即只有一个入度为零的点(源点)和只有一个出度为零的点(汇点). 关键路径:是从开始点到完成点的最长路径的长度.路径的长度是边上活动耗费的时间.如上图