1、重要概念
(1)AOE (Activity OnEdges)网络 如果在无有向环的带权有向图中用有向边表示一个工程中的各项活动(Activity),用边上的权值表示活动的持续时间(Duration),用顶点表示事件(Event),则这样的有向图叫做用边表示活动的网络,简称AOE (Activity On Edges)网络。AOE网是一个带权的有向无环图。
AOE网络在某些工程估算方面非常有用。例如,可以使人们了解:
a、完成整个工程至少需要多少时间(假设网络中没有环)?
b、为缩短完成工程所需的时间, 应当加快哪些活动?
(2)关键路径(Critical Path) 在AOE网络中, 有些活动顺序进行,有些活动并行进行。从源点到各个顶点,以至从源点到汇点的有向路径可能不止一条。这些路径的长度也可能不同。完成不同路径的活动所需的时间虽然不同,但只有各条路径上所有活动都完成了,整个工程才算完成。因此,完成整个工程所需的时间取决于从源点到汇点的最长路径长度,即在这条路径上所有活动的持续时间之和。这条路径长度最长的路径就叫做关键路径(Critical Path)。
如图1就是一个AOE网,该网中有11个活动和9个事件。每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。如事件v5表示a4和a5活动已经完成,a7和a8活动可以开始。每个弧上的权值表示完成相应活动所需要的时间,如完成活动a1需要6天,a8需要7天。
图1
AOE网常用于表示工程的计划或进度。由于实际工程只有一个开始点和一个结束点,因此AOE网存在唯一的入度为0的开始点(又称源点)和唯一的出度为0的结束点(又称汇点),例如图1的AOE网从事件v1开始,以事件v9结束。同时AOE网应当是无环的。
(3)算法描述中要用到的几个表达
e(i)表示活动ai的最早开始时间,l(i)表示活动ai的最迟开始时间; l(i)-e(i)表示完成活动的时间余量。事件vi 的最早开始时间表示为ve(i), 事件vi 的最迟允许开始时间vl(i)。
l[k] = e[k]的活动就是关键活动。为求得e[k]与l[k],需要先求得从源点v0到各个顶点vi 的 ve[i] 和 vl[i]。如果活动ai 由弧<j,k>表示,其持续时间记为dut(<j,k>),则有如下关系:
e(i)=ve(j)
l[i] = vl[k] – dut(<i, k>)
求ve(j)和vl(j)需分两步进行:
(1)
其中, T是所有以第j个顶点为头的弧的集合。
(2)
其中, S是所有以第i个顶点为尾的集合。
这两个递推公式的计算必须分别在拓扑有序及逆拓扑有序的前提下进行。也就是说,ve(j-1)必须在vj的所在前驱的最早发生时间求得之后才能确定,而vl(j-1)则必须在vj 的所有后继的最迟发生时间求得之后才能确定。因此,可以在拓扑排序的基础上计算ve(j-1)和vl(j-1)。
2、算法的基本思想
3、算法的C语言描述
4、算法的C语言实现
#include "SqStack.h"
#include "Queue.h"
#include "StoreType.h"
#include "GraphOper.h"
#define N 30
int vl[N],ve[N];
void Input(ALGraph &G)
{
int i,j,k,w;
char v1[5],v2[5];
ArcNode *p;
printf("Input vexnum & arcnum:\n");
scanf("%d",&G.vexnum);
scanf("%d",&G.arcnum);
printf("Input Vertices:\n");
for (i=0;i<G.vexnum;i++)
{ scanf("%s",G.vertices[i].data);//注意点,解说
G.vertices[i].firstarc=NULL;
}//for
printf("Input Arcs(vb ve w)以空格分开这3个数据,回车另一组数据:\n");
for(k=0;k<G.arcnum;k++)
{
scanf("%s %s %d",v1,v2,&w);
i=LocateVex(G,v1);
j=LocateVex(G,v2);
p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=j;
p->info = w;
p->nextarc=G.vertices[i].firstarc;
G.vertices[i].firstarc=p;
printf("Next\n");
}//for
}//Input
void FindInDegree(ALGraph &G)
{
int i,j;
for(i=0;i<G.vexnum;i++)
{
G.vertices[i].count=0;
}//for
for(j=0;j<G.vexnum;j++)
{
for(ArcNode*p=G.vertices[j].firstarc;p;p=p->nextarc)
G.vertices[p->adjvex].count++;
}//for
}//FindInDegree
Status TopologicalOrder(ALGraph &G,SqStack &T)
{
//G用邻接表存储,T为拓扑序列顶点栈,S为零入度顶点栈
//若G无回路,则用栈T返回G的一个拓扑序列,且函数值为OK,否则返回ERROR
int count=0;
SqStack S;
InitStack(S);
for(int i=0;i<G.vexnum;i++) ve[i]=0;
for(int j=0;j<G.vexnum;j++)
if(!G.vertices[j].count)Push(S,j);
while(!StackEmpty(S))
{
int m=0;
Pop(S,m);
Push(T,m);
++count;
for(ArcNode*p=G.vertices[m].firstarc;p;p=p->nextarc)
{
intk=p->adjvex;
if((--G.vertices[k].count)==0) Push(S,k);
if((ve[m]+p->info)>ve[k]) ve[k]=ve[m]+p->info;
}//for
}//while
if(count<G.vexnum) return ERROR;
else return OK;
}//Topological
Status CriticalPath(ALGraph &G,SqStack &T)
{
int j;
if(!TopologicalOrder(G,T)) return ERROR;
//for(int i=0;i<G.vexnum;i++)vl[i]=ve[i];
j=ve[0];
for(int i=1;i<G.vexnum;i++) // j=Max(ve[]) 完成点的值
if(ve[i]>j)
j=ve[i];
for(int i=0;i<G.vexnum;i++) // 初始化顶点事件的最迟发生时间(最大值)
vl[i]=j; // 完成点的最早发生时间
while(!StackEmpty(T))
{
int m;
Pop(T,m);
for(ArcNode*p=G.vertices[m].firstarc;p;p=p->nextarc)
{
intk=p->adjvex,dut=p->info;
if((vl[k]-dut)<vl[m])vl[m]=vl[k]-dut;
}//for
}//while
for(intj=0;j<G.vexnum;j++)
for(ArcNode*p=G.vertices[j].firstarc;p;p=p->nextarc)
{
intk=p->adjvex,dut=p->info;
intee,el;
chartag;
ee=ve[j];
el=vl[k]-dut;
tag=((ee==el)?'*' : '#');
printf("(v%d->v%d):(dut:%d),(ee:%d),(el:%d)(%c)\n",j+1,k+1,dut,ee,el,tag);
}//for
}//CriticalPath
int main()
{
ALGraph G;
SqStack T;
InitStack(T);
Input(G);
FindInDegree(G);
CriticalPath(G,T);
return OK;
}
5、需要说明的地方
(1)上面的图1中的关键路径就为
(2)影响关键活动的因素是多方面的,任何一项活动持续时间的改变都会影响关键路径的改变。关键活动的速度提高是有限度的,只有在不改变网的关键路径的情况下,提高关键活动的速度才有效。如果网中有几条关键路径,那么单是提高一条关键路径上的关键活动的速度,还不能导致整个工程缩短工期,而必须同时提高几条关键路径上的活动的速度。
(3)注意在上面中的程序中Pop()函数与我们前面介绍的有点不同:
Status Pop(SqStack&S,int &e)
{
if(S.top==S.base)
return ERROR;
S.top--;
e=*(S.top);
return OK;
}
(4)注意
算法中的这一句:
vl[0..G.vexnum-1]=ve[G.vexnun-1];
如果在程序中写成:for(int i=0;i<G.vexnum;i++) vl[i]=ve[i];
是得不出正确结果的。应当写成如下所示:
j=ve[0];
for(int i=1;i<G.vexnum;i++) //j=Max(ve[]) 完成点的值
if(ve[i]>j)
j=ve[i];
for(int i=0;i<G.vexnum;i++) // 初始化顶点事件的最迟发生时间(最大值)
vl[i]=j; // 完成点的最早发生时间
关于这一点,我也很想知道严老师在这一点上的算法描述中的确切含意,还是我理解错vl[0..G.vexnum-1]=ve[G.vexnun-1]这一句的真正含意了。