前面我们简要地介绍了AOE网和关键路径的一些概念,本文接着对求解关键路径程序的主要函数进行分析。现有一AOE网图如图7-9-4所示,我们使用邻接表存储结构,注意与拓扑排序时邻接表结构不同的地方在于,这里弧表结点增加了weight域,用来存储弧的权值。
求解事件的最早发生时间etv的过程,就是我们从头至尾找拓扑序列的过程,因此在求关键路径之前,需要先调用一次拓扑序列算法的代码来计算etv 和 拓扑序列列表,我们针对前面讲过的AOV网与拓扑排序的程序进行改进,代码如下(参考《大话数据结构》):
/* 拓扑排序,若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */
bool TopologicalSort(GraphAdjList GL)
{
EdgeNode *pe;
int i, k, gettop;
int top = 0;/* 用于栈指针下标 */
int count = 0;/* 用于统计输出顶点的个数 */
/* 建栈将入度为0的顶点入栈 */
int *stack = (int *)malloc(GL->numVertexes * sizeof(int));
for (i = 0; i < GL->numVertexes; i++)
if (0 == GL->adjList[i].in)
stack[++top] = i;/* 将入度为0的顶点入栈 */
top2 = 0;
etv = (int *)malloc(GL->numVertexes * sizeof(int));
for (i = 0; i < GL->numVertexes; i++)
etv[i] = 0; /* 初始化 */
stack2 = (int *)malloc(GL->numVertexes * sizeof(int));
cout << "TopologicalSort ..." << endl;
while (top != 0)
{
gettop = stack[top--];
cout << GL->adjList[gettop].data << " -> ";
count++; /* 输出i号顶点,并计数 */
stack2[++top2] = gettop; /* 将弹出的顶点序号压入拓扑序列的栈 */
for (pe = GL->adjList[gettop].firstedge; pe; pe = pe->next)
{
k = pe->adjvex;
/* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */
if (!(--GL->adjList[k].in))
stack[++top] = k;
/* 求各顶点事件的最早发生时间etv值 */
if ((etv[gettop] + pe->weight) > etv[k])
etv[k] = etv[gettop] + pe->weight;
}
}
cout << endl;
if (count < GL->numVertexes)
return false;
else
return true;
}
在程序开始处我们声明了几个全局变量:
int *etv,*ltv; /* 事件最早发生时间和最迟发生时间数组,全局变量 */ int *stack2; /* 用于存储拓扑序列的栈 */ int top2; /* 用于stack2的指针 */
其中stack2用来存储拓扑序列,以便后面求关键路径时使用。
上面的拓扑排序函数中除了增加了第12~19行,29行,38~39行,其他跟前面讲过的AOV网与拓扑排序没什么区别。
第12~19行初始化全局变量etv数组、top2和stack2的过程。第29行就是将本来要输出的拓扑序列压入全局栈stack2中。第38~39行很关键,是求etv数组的每一个元素的值,具体求值办法参见AOE网和关键路径。
下面来看求关键路径的算法代码。
/* 求关键路径,GL为有向网,输出G的各项关键活动 */
void CriticalPath(GraphAdjList GL)
{
EdgeNode *pe;
int i, j, k, gettop;
int ete, lte;/* 声明活动最早发生时间和最迟发生时间变量 */
TopologicalSort(GL);/* 求拓扑序列,计算数组etv和stack2的值 */
ltv = (int *)malloc(GL->numVertexes * sizeof(int)); /* 事件最早发生时间数组 */
for (i = 0; i < GL->numVertexes; i++)
ltv[i] = etv[GL->numVertexes - 1];/* 初始化 */
cout << "etv : ";
for (i = 0; i < GL->numVertexes; i++)
cout << etv[i] << ' ';
cout << endl;
while (top2 != 0)/* 出栈是求ltv */
{
gettop = stack2[top2--];
/* 求各顶点事件的最迟发生时间ltv值 */
for (pe = GL->adjList[gettop].firstedge; pe; pe = pe->next)
{
k = pe->adjvex;
if (ltv[k] - pe->weight < ltv[gettop])
ltv[gettop] = ltv[k] - pe->weight;
}
}
cout << "ltv : ";
for (i = 0; i < GL->numVertexes; i++)
cout << ltv[i] << ' ';
cout << endl;
/* 求ete,lte和关键活动 */
for (j = 0; j < GL->numVertexes; j++)
{
for (pe = GL->adjList[j].firstedge; pe; pe = pe->next)
{
k = pe->adjvex;
ete = etv[j];/* 活动最早发生时间 */
lte = ltv[k] - pe->weight;/* 活动最迟发生时间 */
if (ete == lte) /* 两者相等即在关键路径上 */
cout << "<v" << GL->adjList[j].data << " - v"
<< GL->adjList[k].data << "> length: " << pe->weight << endl;
}
}
}
函数第7行调用求拓扑序列的函数,执行完毕后,全局数组etv和栈stack2 如图7-9-6所示,top2 = 10,也就是说,对于每个事件的最早发生时间,我们已经计算出来了。
第11~12行初始化全局变量ltv数组,因为etv[9] = 27,所以数组ltv值现在为全27。
第19~29行是计算ltv 数组的循环,具体方法参见AOE网和关键路径。
当程序执行到第36行,etv和ltv数组的值如图7-9-9
比如etv[1] = 3, 而ltv[1] = 7,表示如果单位是天的话,哪怕v1整个事件在第7天才开始,也可以保证整个工程的按期完成,可以提前v1事件开始时间,但最早也得第3天开始。
第36~47行是求另两个变量,活动最早开始时间ete和活动最晚开始时间lte,并对相同下标的它们进行比较。两重循环嵌套是对邻接表的顶点和每个顶点的弧表遍历,具体方法参见AOE网和关键路径,举例来说,如图7-9-10,当j = 0时,当k = 2, ete = lte, 表示
弧<v0, v2> 是关键路径,因此打印;当k = 1, ete != lte, 故弧<v0, v1> 不是关键路径。
j = 1 一直到 j = 9为止,做法是完全相同的,最后输出的结果如下图,最终关键路径如图7-9-11所示。