一、概念
事件:瞬时间/刹那间发生的一件事
活动:一个完整的流程,可能由很多事件组成。
AOV网:在DAG(无环有向图)中,用顶点表示的活动,用无权有向边表示活动之间的次序。
-----> 工程中小项目的次序
AOE网(Acitivity On Edge):在DAG中,用顶点表示的事件,用带权有向边表示活动,边权表示活动持续的时间。
----->工程中小项目的次序,每个小项目多久做完,整个工程多久做完(比AOV更详细)
其中V1
,
V2
,
V3.......V9
表示事件,
a1.........a11
表示活动,活动的取值表示完成该活动所需要的 时间,如a1 = 6
表示完成活动
a1
所需要的时间为
6
天。此外,每⼀事件
Vi
表示在它之前的活动已经完成, 在它之后的活动可以开始,如V5
表示活动
a4
和
a5
已经完成,活动
a7
和
a8
可以开始了。
二、整个工程多久做完?
AOE网中 有些活动是可以并行进行的,从起点事件到终点事件 有多条路 径,并且每条路径的长度是不一样的,当所有路径上的活动都完成时 才标 志着整个工程的完成,那么整个工程所需的时间:最长的路径的长度。那么就把最长的路径称为关键路径。关键路径上的活动称为关键活动。
--->关键路径不唯一 我们需要关注所有的关键路径
拿到一个AOE网时,需要找到所有的关键路径。----> 如何在AOE网中找到关键路径?
关键路径:从起点到终点,最长的路径(们),由关键活动组成的
关键活动:不能延期。 最早的开始时间==最晚开始时间
非关键活动:最早的开始时间 != 最晚开始时间
三、如何计算关键路径
(1)事件的最早发生时间(
ETV
, Earlieast Time Of Vertex):
起点事件start的最早发生时间:ETV[s]=0;
其他事件的最早发生时间 基于topo序列去算 ETV[j]=max(ETV[j],ETV[i]+w)
![](https://i-blog.csdnimg.cn/blog_migrate/8d20fa55e4559b6ed41e3780811c9491.png)
(2)事件的最晚发生时间(
LTV
, Latest Time Of Vertex):
对于终点事件endl最早发生和最晚发生是同一个时间
终点事件end的最晚发生时间 LTV[e]=ETV[e]
倒着算 基于逆topo序列 LTV[i]=min(LTV[i],LTV[j]-w)
(3)活动的最早开始时间(ETE):这个活动最早开始做的时间。
等于该边 起点事件的ETV(最早发生时间);
ETE= ETV[i]
(4)活动的最晚开始时间(
LTE
):这个活动最晚可以可以开始做的时间, 如果晚于这个时间 就会延期
保证终点事件 DDL之前( LTV)按时发生
LTE=该边终点事件的LTV(最晚发生时间)-该边权值
LTE=LTV[j]-w
(5)关键活动 LTE==ETE--->关键路径
四、代码
#include <stdio.h>
#include <stdlib.h>
#define INF 65535
//链栈
typedef struct stackNode
{
int data;//存放入栈节点的下标
struct stackNode *next;
}snode,*stack;
//初始化栈
stack inits()
{
stack s=(snode*)malloc(sizeof(snode));
s->next=NULL;
return s;
}
//入栈函数:
stack ppush(stack s,int e)
{
snode* p=(snode*)malloc(sizeof(snode));
p->data=e;
p->next=s->next;
s->next=p;
return s;
}
//出栈-->栈顶数据返回
stack ppop(stack s,int *e)
{
if(s->next==NULL)
{
(*e)=-1;
return s;
}
snode* p=s->next;
(*e)=p->data;
s->next=p->next;
p->next=NULL;
free(p);
p=NULL;
return s;
}
//------------------------
//邻接表存带权有向图-----> AOE网
//边单链表节点结构
typedef struct edgeNode
{
int data;//存放入栈节点的下标
struct edgeNode *next;
int w;//1.边权
}enode;
//顶点数组
typedef struct vNode
{
char d;
struct edgeNode *first;
}Graph;
Graph g[105];
int n,m;
int ind[105];//入度
int etv[105];//事件的最早发生时间
int ltv[105];//事件的最晚发生时间
//char topo[105];//保存拓扑序列
int topo[105];//保存拓扑序列----基于顶点下标
int find(char x)
{
for(int i=0;i<n;i++)
{
if(g[i].d==x)
{
return i;
}
}
}
void toposort()
{
stack s=inits();//初始化栈
for(int i=0;i<n;i++)
{//初始化etv数组
etv[i]=0;
}
for(int i=0;i<n;i++)
{
if(ind[i]==0)
{
s=ppush(s,i);
}
}
int e;//出栈的点的下标
int k=0;//topo数组的下标;
int flag=0;
for(int i=1;i<=n;i++)
{
s=ppop(s,&e);
topo[k]=e;//出栈的顶点的下标 保存在topo序列里面
k++;
enode* p=g[e].first;
int j;
while(p!=NULL)
{
j=p->data;//j就是e的出边邻接点 e---->j
if(etv[j]<(etv[e]+p->w))
{//求etv
etv[j]=etv[e]+p->w;
}
ind[j]--;
if(ind[j]==0)
{
s=ppush(s,j);
}
p=p->next;
}
}
}
void CriticalPath()
{
int end=topo[n-1];//0 ~~ n-1
for(int i=0;i<n;i++)
{
//初始化ltv,将每个点的最晚发生时间都初始化成终点的最早发生时间即可
ltv[i]=etv[end];
}
int x;
//逆拓扑序 更新ltv
for(int i=n-2;i>=0;i--)
{
x=topo[i];//x点的最晚发生时间
enode* p=g[x].first;
while(p!=NULL)
{
int j=p->data;//x-->j
if(ltv[x]>(ltv[j]-p->w))
{
ltv[x]=ltv[j]-p->w;
}
p=p->next;
}
}
printf("以下是关键活动:\n");
//遍历所有的边,求每个边的ete lte
int ete,lte;
for(int i=0;i<n;i++)
{
for(enode* p=g[i].first;p!=NULL;p=p->next)
{
int j=p->data;///i--->j
ete=etv[i];
lte=ltv[j]-p->w;
if(ete==lte)
{
printf("%c %c\n",g[i].d,g[j].d);
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++)
{
getchar();
scanf("%c",&g[i].d);
g[i].first=NULL;
}
char x,y;
int xi,yi,wi;
for(int i=0;i<m;i++)
{
getchar();
scanf("%c %c %d",&x,&y,&wi);
xi=find(x);
yi=find(y);
//统计入度
ind[yi]++;
enode* p=(enode*)malloc(sizeof(enode));
p->data=yi;
p->w=wi;//
p->next=g[xi].first;
g[xi].first=p;
}
toposort();
CriticalPath();
return 0;
}
/*
9 11
A B C D E F G H I
A B 6
A C 4
A D 5
B E 1
C E 1
D F 2
E G 9
E H 7
F H 4
G I 2
H I 4
*/