0x01.相关概念
- AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity On Vertex Network)。
- 拓扑序列:设 是一个具有 个顶点的有向图, 中的顶点序列 满足若从顶点 到 有一条路径,则在顶点序列中顶点 必在顶点 之前。我们称这样的顶点序列为一个拓扑序列。
- 拓扑排序:对一个有向图构造拓扑序列的过程。
- AOE网:在一个表示活工程的有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)。
- 关键路径:我们把路径上各个活动所持续时间之和称为路径长度,从源点到汇点具有最大长度的路径称为关键路径。
- 关键活动:在关键路径上的活动称为关键活动。
0x02.理解AOV网和AOE网
AOV网和AOE网都是表示工程的有向图,AOV网重在表示yo优先关系,AOE网重在表示持续时间。
AOV网:
AOE网:
0x03.拓扑排序算法
基本原理:
从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点,如果还存在,说明存在环,不能构成拓扑序列。
由于需要入度信息和删除操作,需要用到邻接表结构,但还需要入度信息,十字链表太过麻烦,最好的办法是手动输入一个顶点的入度,这样顶点的结构就由 in,data,firstedge,构成。需要用栈来保存入度为0的顶点。
代码:
int TopologicalAort(GraphAdjList GL)
{
EdgeNode* e;
int i, k, gettop;
int top = -1;//栈指针
int count = 0;//统计输出顶点个数
int* stack;//此栈存储入度为0的顶点
stack = (int*)malloc(GL.numv*sizeof(int));
for (i = 0;i < GL.numv; i++)
{
if (GL.adjList[i].in == 0)
{
stack[++top] = i;
}
}
while (top != -1)
{
gettop=stack[top--];//出栈
printf("%c->", GL.adjList[gettop].data);//输出节点
count++;
for (e = GL.adjList[i].firstedge; e; e = e->next)//访问邻接边
{
k = e->adjvex;
if (!(--GL.adjList[k].in))
{
stack[++top] = k;
}
}
}
if (count < GL.numv)
{
return false;
}
else
{
return true;
}
}
0x04.关键路径
因为AOE网涉及时间的关系,首先需要了解几个概念。
- 事件的最早发生时间 etv(earliest time of vertex):顶点 的最早发生时间。
- 事件的最晚发生时间 ltv(lastest time of vertex):顶点 的最晚发生时间。
- 活动的最早开工时间 ete(earliest time of edge):弧 的最早发生时间。
- 活动的最晚开工时间 lte(lastest time of edge):弧 的最晚发生时间.。
首先我们需要求出事件的最早发生事件etv,这个步骤,可以在拓扑排序的同时求出。
为求关键路径改进的拓扑排序算法:
//首先需要定义几个全部变量
int* etv, * ltv;//求每个顶点的最早发生时间和最晚发生时间
int* stack2;//存储拓扑序列
int top2;
int TopologicalAort1(GraphAdjList GL)
{
EdgeNode* e;
int i, k, gettop;
int top = -1;
int count = 0;
int* stack;
stack = (int*)malloc(GL.numv * sizeof(int));
for (i = 0; i < GL.numv; i++)
{
if (GL.adjList[i].in == 0)
{
stack[++top] = i;
}
}
top2 = -1;
etv = (int*)malloc(GL.numv * sizeof(int));
for (i = 0; i < GL.numv; i++)//初始化事件的最早发生时间
{
etv[i] = 0;
}
stack2= (int*)malloc(GL.numv * sizeof(int));
while (top != -1)
{
gettop = stack[top--];
count++;
stack[++top2] = gettop;//存入拓扑序列
for (e = GL.adjList[gettop].firstedge; e; e = e->next)
{
k = e->adjvex;
if (!(--GL.adjList[k].in))
{
stack[++top] = k;
}
if ((etv[gettop] + e->weight) > etv[k])//求出etv数组
{
etv[k] = etv[gettop] + e->weight;
}
}
}
if (count < GL.numv)
{
return false;
}
else
{
return true;
}
}
这个改进的目的就在于求出etv数组,求etv数组的方法就是找到这个顶点的起始点,然后求出起始点到这个顶点的最长路径。
当 时, 。例如:假如顶点V3有<V1,V3>,<V2,V3>,两条弧,那么etv[3]=max{etv[2]+len<V2,V3>,etv[1]+len<V1,V3>}。
关键路径算法:
void CrticalPath(GraphAdjList GL)
{
EdgeNode* e;
int i, gettop, k, j;
TopologicalAort1(GL);
int ete, lte;//活动的最早发生时间和最晚发生时间
ltv= (int*)malloc(GL.numv * sizeof(int));
for (i = 0; i < GL.numv; i++)//初始化ltv数组
{
ltv[i] = etv[GL.numv-1];//全部设置为etv中的最大值
}
while (top2 != -1)
{
gettop = stack2[top2--];
for (e = GL.adjList[gettop].firstedge; e; e = e->next)
{
k = e->adjvex;
if (ltv[k] - e->weight < ltv[gettop])//求出ltv数组
{
ltv[gettop] = ltv[k] - e->weight;
}
}
}
for (j = 0; j < GL.numv; j++)//对每个顶点的弧表遍历
{
for (e = GL.adjList[j].firstedge; e; e = e->next)
{
k = e->adjvex;
ete = etv[j];
lte = ltv[k] - e->weight;
if (ete == lte)//活动的最早开工时间等于最晚开工时间,是关键路径
{
printf("(%c->%c),weight=%d", GL.adjList[j].data, GL.adjList[k].data, e->weight);
}
}
}
}
算法原理分析:
在算法的前半部分,利用已产生的拓扑序列的栈,计算出了 ltv 数组,这个最晚开工时间应该从后向前推导,当 时,,因为最后一个去完成的活动肯定是最后一个活动,别无它选,如果不是最后一个活动,那么[,例如:假如一个顶点V7有<V7,V8>,<V7,V9>两条弧,那么ltv[7]应该等于min{ltv[8]-len<V7,V8>(这条弧的长),ltv[9]-len<V7,V9>。
在确定最短路径的时候,出现了ete与lte,这两个概念都是针对于弧来说的,ete,弧的最早开工时间,应该是等于这条弧的弧尾的最早发生时间,lte,弧的最晚开工时间,应该是等于弧头的最晚开始时间-这条弧的权值。
如果ete等于lte就说明这条弧是关键路径上的弧,因为如果一条弧的最早开始时间和最晚开始时间不相等,就说明,中间有空闲,还可以去完成其它的活动,那么这就不是关键路径了,关键路径是整个网中路径最长的,从开始到结束,始终都是没有空闲的。
本章结束。