// algo7-5.cpp 求关键路径。实现算法7.13、7.14的程序
#include"c1.h"
#define MAX_NAME 5 // 顶点字符串的最大长度+1
typedef int InfoType;
typedef char VertexType[MAX_NAME]; // 字符串类型
#include"c7-21.h"
#include"bo7-2.cpp"
#include"func7-1.cpp"
int ve[MAX_VERTEX_NUM]; // 事件最早发生时间,全局变量(用于算法7.13和算法7.14)
typedef int SElemType; // 栈元素类型
#include"c3-1.h" // 顺序栈的存储结构
#include"bo3-1.cpp" // 顺序栈的基本操作
Status TopologicalOrder(ALGraph G,SqStack &T)
{ // 算法7.13 有向网G采用邻接表存储结构,求各顶点事件的最早发生时间ve(全局变量)。T为拓扑序列
// 顶点栈,S为零入度顶点栈。若G无回路,则用栈T返回G的一个拓扑序列,且函数值为OK;否则为ERROR
int i,k,count=0; // 已入栈顶点数,初值为0
int indegree[MAX_VERTEX_NUM]; // 入度数组,存放各顶点当前入度数
SqStack S;
ArcNode *p;
FindInDegree(G,indegree); // 对各顶点求入度indegree[],在func7-1.cpp中
InitStack(S); // 初始化零入度顶点栈S
printf("拓扑序列:");
for(i=0;i<G.vexnum;++i) // 对所有顶点i
if(!indegree[i]) // 若其入度为0
Push(S,i); // 将i入零入度顶点栈S
InitStack(T); // 初始化拓扑序列顶点栈
for(i=0;i<G.vexnum;++i) // 初始化ve[]=0(最小值,先假定每个事件都不受其它事件约束)
ve[i]=0;
while(!StackEmpty(S)) // 当零入度顶点栈S不空
{
Pop(S,i); // 从栈S将已拓扑排序的顶点j弹出
printf("%s ",G.vertices[i].data);
Push(T,i); // j号顶点入逆拓扑排序栈T(栈底元素为拓扑排序的第1个元素)
++count; // 对入栈T的顶点计数
for(p=G.vertices[i].firstarc;p;p=p->nextarc)
{ // 对i号顶点的每个邻接点
k=p->data.adjvex; // 其序号为k
if(--indegree[k]==0) // k的入度减1,若减为0,则将k入栈S
Push(S,k);
if(ve[i]+*(p->data.info)>ve[k]) // *(p->data.info)是<i,k>的权值
ve[k]=ve[i]+*(p->data.info); // 顶点k事件的最早发生时间要受其直接前驱顶点i事件的
} // 最早发生时间和<i,k>的权值约束。由于i已拓扑有序,故ve[i]不再改变
}
if(count<G.vexnum)
{
printf("此有向网有回路\n");
return ERROR;
}
else
return OK;
}
Status CriticalPath(ALGraph G)
{ // 算法7.14 G为有向网,输出G的各项关键活动
int vl[MAX_VERTEX_NUM]; // 事件最迟发生时间
SqStack T;
int i,j,k,ee,el,dut;
ArcNode *p;
if(!TopologicalOrder(G,T)) // 产生有向环
return ERROR;
j=ve[0]; // j的初值
for(i=1;i<G.vexnum;i++)
if(ve[i]>j)
j=ve[i]; // j=Max(ve[]) 完成点的最早发生时间
for(i=0;i<G.vexnum;i++) // 初始化顶点事件的最迟发生时间
vl[i]=j; // 为完成点的最早发生时间(最大值)
while(!StackEmpty(T)) // 按拓扑逆序求各顶点的vl值
for(Pop(T,j),p=G.vertices[j].firstarc;p;p=p->nextarc)
{ // 弹出栈T的元素,赋给j,p指向j的后继事件k,事件k的最迟发生时间已确定(因为是逆拓扑排序)
k=p->data.adjvex;
dut=*(p->data.info); // dut=<j,k>的权值
if(vl[k]-dut<vl[j])
vl[j]=vl[k]-dut; // 事件j的最迟发生时间要受其直接后继事件k的最迟发生时间
} // 和<j,k>的权值约束。由于k已逆拓扑有序,故vl[k]不再改变
printf("\ni ve[i] vl[i]\n");
for(i=0;i<G.vexnum;i++) // 初始化顶点事件的最迟发生时间
{
printf("%d %d %d",i,ve[i],vl[i]);
if(ve[i]==vl[i])
printf(" 关键路径经过的顶点");
printf("\n");
}
printf("j k 权值ee el\n");
for(j=0;j<G.vexnum;++j) // 求ee,el和关键活动
for(p=G.vertices[j].firstarc;p;p=p->nextarc)
{
k=p->data.adjvex;
dut=*(p->data.info); // dut=<j,k>的权值
ee=ve[j]; // ee=活动<j,k>的最早开始时间(在j点)
el=vl[k]-dut; // el=活动<j,k>的最迟开始时间(在j点)
printf("%s→%s %3d %3d %3d ",G.vertices[j].data,G.vertices[k].data,dut,ee,el);
// 输出各边的参数
if(ee==el) // 是关键活动
printf("关键活动");
printf("\n");
}
return OK;
}
void main()
{
ALGraph h;
printf("请选择有向网\n");
CreateGraph(h); // 构造有向网h,在bo7-2.cpp中
Display(h); // 输出有向网h,在bo7-2.cpp中
CriticalPath(h); // 求h的关键路径
}
代码的运行结果:
请选择有向网(见图765)
请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): 1
请输入图的顶点数,边数: 6,8
请输入6个顶点的值(<5个字符):
V1 V2 V3 V4 V5 V6
请输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):
3 V1 V2
2 V1 V3
2 V2 V4
3 V2 V5
4 V3 V4
3 V3 V6
2 V4 V6
1 V5 V6
有向网(见图766)
6个顶点:
V1 V2 V3 V4 V5 V6
8条弧(边):
V1→V3 :2 V1→V2 :3
V2→V5 :3 V2→V4 :2
V3→V6 :3 V3→V4 :4
V4→V6 :2
V5→V6 :1
拓扑序列:V1 V2 V5 V3 V4 V6
i ve[i] vl[i] (见图767)
0 0 0 关键路径经过的顶点
1 3 4
2 2 2 关键路径经过的顶点
3 6 6 关键路径经过的顶点
4 6 7
5 8 8 关键路径经过的顶点
j k 权值ee el
V1→V3 2 0 0 关键活动
V1→V2 3 0 1
V2→V5 3 3 4
V2→V4 2 3 4
V3→V6 3 2 5
V3→V4 4 2 2 关键活动
V4→V6 2 6 6 关键活动
V5→V6 1 6 7
图766 是邻接表的示意图。为直观和方便起见,表结点中邻接顶点的序号直接用其
名称代替,动态生成的权值也直接写在权值的指针域中。
运行algo7-5.cpp 用到2 个辅助数组:事件(顶点)最早发生时间ve[]和事件最迟发生
时间vl[]。
顶点i 的事件(顶点)最早发生时间ve[i]取决于其直接前驱事件的发生时间和二者之间
活动的持续时间(弧的权值)。如图765 中,V2 事件的最早发生时间取决于V1 的ve[]和
<V1,V2>的权值3。即V2 的ve[]等于V1 的ve[]+3。如果顶点i 有多个直接前驱事件则
ve[i] 取最大值, 如V4 的ve[] 取决于V2 的ve[]+<V2,V4> 的权值2 与V3 的
ve[]+<V3,V4>的权值4 这2 者中的大值。没有直接前驱事件的顶点,其ve[]=0(是最小
值),如顶点V1。由于求顶点的ve[]要求其直接前驱的ve[]已知,故应先对有向网进行拓
扑排序。排序前设所有顶点的ve[]初值=0(最小值),当出现较大的值,则用这个大值更新
ve[]。调用TopologicalOrder()后,ve[]如以上程序运行结果所示。
所谓事件最迟发生时间是指在不影响工期的情况下,某事件可以最迟发生的时间。顶
点i 的事件(顶点)最迟发生时间vl[i]取决于其直接后继事件的最迟发生时间和二者之间活
动的持续时间(弧的权值)。如图765 中,V4 事件的最迟发生时间取决于V6 的vl[]和
<V4,V6>的权值2。即V4 的vl[]等于V6 的vl[]-2。如果顶点i 有多个直接后继事件则
vl[i]取最小值。如V3 的vl[]取决于V4 的vl[]-<V3,V4>的权值4 与V6 的vl[]-<V3,V6>
的权值3 这2 者中的小值。没有直接后继事件的顶点,其vl[]=ve[](是最大值,已先期求
出),如顶点V6。由于求顶点的vl[]时,要求其直接后继的vl[]已知,故应先形成有向网
的逆拓扑序列。TopologicalOrder()将已拓扑排序的顶点入栈T,形成逆拓扑序列。排序前
设所有顶点的vl[]初值等于没有后继的那个顶点的vl[](最大值),本例中这个顶点是
V6。当出现较小的值,则用这个小值更新vl[]。调用CriticalPath(),vl[]如以上程序运行
结果所示。
若对于顶点i,有ve[i]=vl[i],即事件最早发生时间等于事件最迟发生时间。说明为
保证工期,事件(顶点)i 的发生时间不可变更。如果变小,则前面的活动(入弧)还没完
成;如果变大,则影响后继事件按时完成。因此,顶点i 是关键路径要经过的点。如以上
程序运行结果所示,V1、V3、V4 和V6 是关键路径要经过的顶点。但光根据这些顶点,
还不能确定关键路径。如图765 中,虽然V3、V4 和V6 是关键路径要经过的顶点,但
弧<V3,V4>、<V3,V6>和<V4,V6>中哪个是关键路径还不清楚。
如果一个活动(弧)<j,k>,它的前端事件的最早发生时间ve[j]+<j,k>的权值等于
vl[k](后端事件的最迟发生时间),那么这个活动的发生时间就没有变更的余地,它就是整
个关键路径的一部分。求得ve[]和vl[]后,对于每一个弧<j,k>,判断它的ve[j]是否等于
它的vl[k]+dut(弧的权值),可求出所有关键路径。图767 中粗箭头弧是关键路径。