在一个表示工程的带权有向图中,用顶点表示事件,有向边表示活动,用边上的权值表示活动的持续事件,这种有向图我们称之为AOE网(Activity On Edge Network)
AOE网中入度为0的顶点称为始点或源点,出度为0的顶点成为终点或汇点
AOE网是用来表示工程流程的,它带有明显的工程特性,如果在某顶点所代表的事情发生后,从该顶点出发的各活动才能开始,只有在进入某顶点的各活动都已经结束,该顶点所代表的事件才能发生
由于工程一般只有一个源点和一个汇点,所以AOE网同样各有一个
AOE网与AOV网的区别:
AOV只描述活动之间的制约关系,而AOE是要建立在活动制约关系没有矛盾的基础上再来分析完成整个工程至少需要多少时间,或怎么缩短工期时间
我们把各个活动持续的时间之和成为
路径长度
,从源点到汇点最大的长度的路径叫做关键路径
,在关键路径的活动叫关键活动
想要缩短工程工期,则必须提高关键路径上的关键活动的速度,它们才是影响工期的狗贼
一个AOV可能具有多条关键路径,唯有提高所有关键路径上的关键活动的速度才能缩短整个工程的工期
所以你明白关键路径的重要性了吧,废话不多说,上求最小关键路径代码(粘贴即可执行)
#include <iostream>
#include<stdlib.h>
#define maxSize 100
using namespace std;
typedef struct EdgeNode{//边结点
int adjvex;
int weight;//边的权重
struct EdgeNode* nextEdge;
}EdgeNode;
typedef struct Vnode{//顶点结点
int data;
int in;//顶点入度
EdgeNode *firstEdge;
}Vnode,AdjList[maxSize];
typedef struct AGraph{//图结点
AdjList adjList;//顶点数组
int n,e;//顶点数和边数
}AGraph;
void createGraph(AGraph &G){//创建图
int i,j,k,w;
EdgeNode *p;
printf("请输入顶点数和边数\n");
scanf("%d %d",&G.n,&G.e);
printf("请输入顶点:\n");
for(i=0;i<G.n;i++){
scanf("%d",&G.adjList[i].data);
G.adjList[i].firstEdge=NULL;
G.adjList[i].in=0;
}
printf("请输入边的顶点下标权值及i j w\n");
for(k=0;k<G.e;k++){
scanf("%d %d %d",&i,&j,&w);
p=(EdgeNode *)malloc(sizeof(EdgeNode));//创建边结点
p->adjvex=j;//边结点下标
p->weight=w; //边结点权值
p->nextEdge=G.adjList[i].firstEdge;//头插法连接边结点
G.adjList[i].firstEdge=p;
G.adjList[j].in++;//边的入度自增长
}
}
int *etv;
int *ltv;
int *stack2;//辅助栈2(动态数组)
int top2;
int topSort(AGraph G){//拓扑排序
int i,j,k,w,count=0;
EdgeNode *p;
int *stack1;
stack1=(int *)malloc(G.n*sizeof(int));
int top1=-1;//初始化栈1
top2=-1;
stack2=(int *)malloc(G.n*sizeof(int));//给辅助栈2动态分配内存空间,并进行初始化
etv=(int *)malloc(G.n*sizeof(int));//给顶点最早开始时间数组动态分配内存空间
for(i=0;i<G.n;i++){
if(G.adjList[i].in==0){//若入度为0则进栈1
stack1[++top1]=i;
}
etv[i]=0;//初始化顶点最早开始时间数组
}
while(top1!=-1){//若栈不为空
i=stack1[top1--];//栈顶出栈
count++;//顶点计数
stack2[++top2]=i;
for(p=G.adjList[i].firstEdge;p;p=p->nextEdge){//遍历出栈顶点的弧表
j=p->adjvex;
G.adjList[j].in--;//入度自减
if(!G.adjList[j].in){//若入度为0,则顶点下标入栈
stack1[++top1]=j;
}
if(etv[i]+p->weight>etv[j]){
etv[j]=etv[i]+p->weight;//事件j最早开始时间=事件i最早开始时间+len(i,j)的最大值
}
}
}
if(count<G.n){//若count比顶点数少,则此拓扑排序是失败的
return 0;
}
return 1;
}
void criticalPath(AGraph G){//关键路径(最早开始时间=最晚开始时间的活动),即决定工期进度的活动
EdgeNode *e;
int i,j,k,ete,lte;
topSort(G);//进行拓扑排序,得到相应的stack2 (因为求活动最晚开始时间需要倒序开始)
ltv=(int *)malloc(sizeof(int)*G.n);//给事件最晚开始时间数组动态分配内存空间
for(i=0;i<G.n;i++){
ltv[i]=etv[G.n-1];//初始化所有事件的最晚开始时间全为项目完成时间(其实只要保证初始值很大就行)
}
while(top2!=-1){//栈不为空
i=stack2[top2--];//顶点序号出栈
for(e=G.adjList[i].firstEdge;e;e=e->nextEdge){//遍历出栈顶点的弧表
j=e->adjvex;
if(ltv[j]-e->weight<ltv[i]){//事件i最晚开始时间=事件j最晚开始时间-len(i,j)中的最小值
ltv[i]=ltv[j]-e->weight;
}
}
}
for(i=0;i<G.n;i++){//遍历所有顶点及其弧表
for(e=G.adjList[i].firstEdge;e;e=e->nextEdge){
j=e->adjvex;
ete=etv[i];//ete是指事件i到事件j的最早开始时间,针对的是活动(即弧),而etv[i]针对的是事件i,这里它们值一致
lte=ltv[j]-e->weight;//事件i到事件j(活动i->j)最晚开始时间=事件j最晚开始时间-len(i,j);
if(ete==lte){//若事件i到j的最早和最晚时间一致则说明它们一刻也不能耽误,它们是决定工程工期的因素
printf("<%d->%d> %d,\n",G.adjList[i].data,G.adjList[j].data,e->weight);//关键路径
}
}
}
}
int main(int argc, char** argv) {
AGraph G;
createGraph(G);
criticalPath(G);
return 0;
}
/*
式例图
i j weight(i->j 权值)
0 2 4
0 1 3
1 4 6
1 3 5
2 5 7
2 3 8
3 4 3
4 7 4
4 6 9
5 7 6
6 9 2
7 8 5
8 9 3
*/