一、拓扑排序
1、基础概念
1)VOE(Activity On Vertex Network):这种用顶点表示活动,用弧表示活动时间的优先关系的有向图称为顶点表示活动的网,简称AOE网。
2)拓扑排序:所谓拓扑排序,就是将AOV网中所有顶点排成一个线性序列,该序列满足:若在AOV网中由顶点vi到顶点vj有一条路径,则在该线性序列中的顶点vi必定在顶点vj之前。
note that:如果一条有向网有环,则经由拓扑排序后,部分有向网中的顶点不会出现在拓扑序列中。the other word,可以通过拓扑排序 来判断一个有向网 是否有环。
2、拓扑排序步骤
1)在有向图中找一条无前驱的顶点 输出它;
2)从图中删除该顶点和所有以它为尾的弧;
3)重复1)和2),直至不存在无前驱的顶点;
4)若此时输出的顶点数小于有向图中的顶点数,则说明有向图中存在环,否则输出的顶点序列即为一个拓扑序列。
3、拓扑排序 伪代码
//有向图用 邻接表存储
//需要几个辅助数组
//indegree[]:存放各个顶点的入度
//topo[]:存放拓扑序列
//栈S:将度为0的顶点入栈
Status TopologicalSort(ALGraph G,int topo[]){
FindIndegree(G,indegree); //找到每个顶点的入度:时间复杂度为O(e);
InitStack(S);
n = G.vexnum;
for(i=0;i<n;++i){
if(!indegree[i]){Push(S,i);}
} //将入度为0的顶点入栈
m=0; //记录拓扑序列中顶点个数
while(!EmptyStack(S)){ //S非空时,执行while循环
Pop(S,v); //将入度为0的顶点出栈
topo[m] = v; //将顶点放入拓扑序列中
++m;
p=G.vextices[v].firstarc; //删除以v为尾的弧,并修改另外一个顶点 的入度信息
while(!p){
j = p->adjvex; //顶点信息
indegree[j] -= 1; //修改j的入度信息
if(!indegree[j]){Push(S,j);} //如果j的入度信息为0,则将j入栈
p = p->nextarc;
}
}
if(m<n){return ERROR;}
else{return OK;}
}
总结:
1、拓扑排序算法:找各个顶点的入度信息时间复杂度为O(e),每个顶点入栈一次出栈一次,时间复杂度为O(n),修改邻接点入度信息时间复杂度为O(e),所以,拓扑排序算法的时间复杂度为O(n+e);
二、关键路径
1、相关概念
1)AOE(activity on edge):AOE网中,顶点代表事件,弧代表活动。AOE网常用于工程计划中,求解影响工程进度的关键路径。下图为一个AOE网,本节以此为例说明关键路径求解方法:
2)关键路径:要估算整项工程完成的最短时间,就是要找一条从源点到汇点的带权路径长度最长的路径,称为关键路径,关键路径上的活动称为关键活动。
2、关键路径求解思路
要求的关键路径 实际上 就是 满足下列条件的活动,即:活动的最早开始时间ve = 活动的最迟开始时间vl;
如果vl-ve > 0,则说明 可以留给活动一些缓冲时间,即使完成稍微晚一点也没关系,因此,不影响工程进度,这类活动,不是关键路径。
从上述的几个事实我们可以看出,要想求 关键路径,实际上就是求 ve=vl 的那些活动,接下来介绍 活动的 ve 和 vl 的求解方法。
活动ve 和 vl 的求解可以通过 求解 事件的ve和vl 来进行。
要求事件的ve和vl需要用到 拓扑排序。接下来具体求解事件的 最早发生时间 和 最迟发生时间。
1)事件vi的最早发生时间 ve(i):
进入事件vi的每个活动都结束后,vi才可以开始进行,因此,vi的最早发生时间 实际上是从 v0到vi的最长路径长度。
ve(0) = 0;
ve(i) = Max{ve(k) + wki};
2)事件vi的最迟发生时间vl(i):
事件vi的发生不得延误vi的每一后继事件的最迟发生时间。
假定vl(n-1) = ve(n-1);
vl(i) = Min{vl(j) - wij};
有了事件的最早 和 最迟 发生时间 以后,我们开始计算 活动(ai=<vi,vj>)的 最早开始时间 和 最晚开始时间:
1)ai的最早开始时间 = 事件vi的最早发生时间,即 ve(i);
2)ai的最迟开始时间 = 事件vj的最迟发生时间 - wij,即 vl(j) - wij;
当ai的 最早开始时间 = ai的最迟开始时间 时,说明ai是关键活动;
3、关键路径求解伪代码
//有向图 用 邻接表 存储
//topo[]:记录各个顶点的拓扑序列,根据这个序列求解 事件 最早/最迟 开始时间
Status CriticalPath(ALGraph G){
//首先构建 有向图的拓扑序列
if(!TopologicalOrder(G,topo)){return ERROR;} //构建拓扑序列
n = G.vexnum;
for(i=0;i<n;++i){ve(i) = 0;} //初始化事件的最早开始时间
for(i=0;i<n;++i){ //更新事件的最早开始时间
v = topo[i];
p = G.vextices[v].firstarc;
while(!p){
j = p->adjvex;
if(ve[j] < ve[v] + p->weight){ve[j] = ve[v] + p->weight;}
p = p->nextarc;
}
}
for(i=0;i<n;++i){vl[i] = ve[n-1];} //初始化事件的最迟开始时间为 事件n-1的最迟开始时间
for(i=n-1;i>=0;--i){ //更新事件的最迟开始时间
v = topo[v];
p = G.vextices[v].firstarc;
while(!p){
j = p->adjvex;
if(vl[v] > vl[j] - p->weight){vl[v] = vl[j] - p->weight;}
p = p->nextarc;
}
}
//求活动的最早 和 最迟开始时间
for(i=0;i<n;++i){
p = G.vextices[i].firstarc;
while(!p){
j = p->adjvex;
e = ve[i];
l = vl[j] - p->weight;
if(e == l){ //满足条件,说明是关键活动
cout<<G.vextices[i]<<G.vextices[j]
}
p = p->nextarc;
}
}
}
总结:
1、拓扑排序的时间复杂度为O(n+e),初始化事件最早/最迟开始时间的时间复杂度为O(n),更新事件最早/最迟开始时间的时间复杂度为O(n+e),寻找关键路径的时间复杂度为O(n+e),因此,算法总得时间复杂度为O(n+e);
2、在实际中,影响工程进程的因素很多,一个活动完工时间的变化,可能导致关键路径的变化;
3、在实际中,一个工程往往有多条关键路径,要想降低工程完成时间,则需要同时对多个 关键路径 进行提速;