知识点
与AOV—网相对应的是AOE—网(Activity On Edge)即边表示活动的网。AOE—网是一个带权的有向无环图,其中,顶点表示事件(Event),弧表示活动,权表示活动持续的时间。通常,AOE—网可用来估算工程的完成时间。
例如,图7.29是一个假想的有11项活动的AOE—网。其中有9个事件 v 1 v_1 v1, v 2 v_2 v2, v 3 v_3 v3, v 4 v_4 v4, ⋯ \cdots ⋯, v 9 v_9 v9,每个事件表示在它之前的活动已经完成,在它之后的活动可以开始。如 v 1 v_1 v1表示整个工程开始, v 9 v_9 v9表示整个工程结束, v 5 v_5 v5表示 a 4 a_4 a4和 a 5 a_5 a5已经完成, a 7 a_7 a7和 a 8 a_8 a8可以开始。与每个活动相联系的数是执行该活动所需的时间。比如,活动 a 1 a_1 a1需要6天, a 2 a_2 a2需要4天等.
由于整个工程只有一个开始点和一个完成点
故在正常的情况(无环)下,网中只有一个人度为零的点(称做源点)和一个出度为零的点(叫做汇点)。
和 AOV-网 不同,对AOE—网有待研究的问题是:(1)完成整项工程至少需要多少时间?(2)哪些活动是影响工程进度的关键?
由于在 AOE-网 中有些活动可以并行地进行,所以完成工程的最短时间是从开始点到完成点的最长路径的长度(这里所说的路径长度是指路径上各活动持续时间之和,不是路径上弧的数目)。路径长度最长的路径叫做关键路径(Critical Path)。假设开始点是 v 1 v_1 v1,从 v 1 v_1 v1到 v i v_i vi的最长路径长度叫做事件 v i v_i vi的最早发生时间。这个时间决定了所有以 v i v_i vi;为尾的弧所表示的活动的最早开始时间。我们用 e ( i ) e(i) e(i)表示活动 a i a_i ai的最早开始时间。还可以定义一个活动的最迟开始时间 l ( i ) l(i) l(i),这是在不推迟整个工程完成的前提下,活动 a i a_i ai最迟必须开始进行的时间。两者之差 l ( i ) − e ( i ) l(i)-e(i) l(i)−e(i)意味着完成活动 a i a_i ai的时间余量。我们把 l ( i ) = e ( i ) l(i)=e(i) l(i)=e(i)的活动叫做关键活动。
显然,关键路径上的所有活动都是关键活动,因此提前完成非关键活动并不能加快工程的进度。例如图7.29中的网,从 v 1 v_1 v1到 v 9 v_9 v9,的最长路径是( v 1 v_1 v1, v 2 v_2 v2, v 5 v_5 v5, v 8 v_8 v8, v 9 v_9 v9),路径长度是18,即 v 9 v_9 v9的最早发生时间是18。而活动 a 6 a_6 a6的最早开始时间是5,最迟开始时间是8,这意味着:如果 a 6 a_6 a6推迟3天开始或者延迟3天完成,都不会影响整个工程的完成。因此,分析关键路径的目的是辨别哪些是关键活动,以便争取提高关键活动的工效, 缩短整个工期。
算法实现
由此得到如下所述求关键路径的算法:
(1)输入 e e e 条弧 ( j , k ) (j,k) (j,k) 建立 AOE-网 的存储结构;
(2)从源点 v 0 v_0 v0 出发,令 v e [ 0 ] = 0 ve[0]=0 ve[0]=0 按拓扑有序求其余各顶点的最早发生时间 v e [ i ] ( 1 ≤ i ≤ n − 1 ) ve[i](1≤i≤n-1) ve[i](1≤i≤n−1)。如果得到的拓扑有序序列中顶点个数小于网中顶点数n则说明网中存在环,不能求关键路径,算法终止;否则执行步骤(3)。
(3)从汇点 v n v_n vn 出发,令 v l [ n − 1 ] = v e [ n − 1 ] vl[n-1]=ve[n-1] vl[n−1]=ve[n−1],按逆拓扑有序求其余各顶点的最迟发生时间 v l [ i ] ( n − 2 ≥ i ≥ 2 ) vl[i](n-2≥i≥2) vl[i](n−2≥i≥2);
(4)根据各顶点的 v e ve ve 和 v l vl vl 值,求每条弧 s s s 的最早开始时间 e ( s ) e(s) e(s) 和最迟开始时间 l ( s ) l(s) l(s) 。若某条弧满足条件 e ( s ) = l ( s ) e(s)=l(s) e(s)=l(s) 则为关键活动。
//算法7.13
int ve[MAX_VERTEX_NUM];
Status TopologicalOrder(ALGraph G, SqStack &T){
int j, k, count, indegree[MAX_VERTEX_NUM];
SqStack S;
ArcNode *p;
FindInDegree(G, indegree);
InitStack(S);
for (j = 0; j < G.vexnum; ++j)
if (!indegree[j])
Push(S, j);
InitStack(T);
count = 0;
for (j = 0; j < G.vexnum; ++j)
ve[j] = 0;
while (!StackEmpty(S)){
Pop(S, j);
Push(T, j);
++count;
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
if (--indegree[k] == 0)
Push(S, k);
if (ve[j] + *(p->info) > ve[k])
ve[k] = ve[j] + *(p->info);
}
}
if (count < G.vexnum)
{
printf("此有向网有回路\n");
return ERROR;
}
else
return OK;
}
//算法7.14
Status CriticalPath(ALGraph G){
int vl[MAX_VERTEX_NUM];
SqStack T;
int i, j, k, ee, el;
ArcNode *p;
char dut, tag;
if (!TopologicalOrder(G, T)) // 产生有向环
return ERROR;
j = ve[0];
for (i = 1; i < G.vexnum; i++) //j=Max(ve[]) 完成点的值
if (ve[i] > j)
j = ve[i];
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)
{
k = p->adjvex;
dut = *(p->info); // dut<j,k>
if (vl[k] - dut < vl[j])
vl[j] = vl[k] - dut;
}
printf(" j k dut ee el tag\n");
for (j = 0; j < G.vexnum; ++j)//求ee,el和关键活动
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
dut = *(p->info);
ee = ve[j];
el = vl[k] - dut;
tag = (ee == el) ? '*' : ' ';
printf("%2d %2d %3d %3d %3d %c\n", j, k, dut, ee, el, tag); // 输出关键活动
}
printf("关键活动为:\n");
for (j = 0; j < G.vexnum; ++j)
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
dut = *(p->info);
if (ve[j] == vl[k] - dut)
printf("%s→%s\n", G.vertices[j].data, G.vertices[k].data); // 输出关键活动
}
return OK;
}
完整代码
#include<bits/stdc++.h>
using namespace std;
typedef int Status;
typedef int Boolean;
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define MAX_NAME 5
typedef int InfoType;
typedef char VertexType[MAX_NAME];
/* --------------------------------- 图的邻接表存储表示 --------------------------------*/
#define MAX_VERTEX_NUM 20
typedef enum {
DG, DN, AG, AN }GraphKind;
typedef struct ArcNode
{
int adjvex;
struct ArcNode *nextarc;
InfoType *info;
}ArcNode;
typedef struct
{
VertexType data;
ArcNode *firstarc;
}VNode, AdjList[MAX_VERTEX_NUM];
typedef struct
{
AdjList vertices;
int vexnum, arcnum;
int kind;
}ALGraph;
/* ----------------------------- 需要用的图的邻接表存储的基本操作 --------------------------*/
int LocateVex(ALGraph G, VertexType u)
{
/* 初始条件: 图G存在,u和G中顶点有相同特征 */
/* 操作结果: 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 */
int i;
for (i = 0; i < G.vexnum; ++i)
if (strcmp(u, G.vertices[i].data) == 0)
return i;
return -1;
}
Status CreateGraph(ALGraph &G){
int i, j, k;
int w; /* 权值 */
VertexType va, vb;
ArcNode *p;
printf("请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): ");
scanf("%d", &G.kind);
printf("请输入图的顶点数和边数(空格隔开): ");
scanf("%d%d", &G.vexnum, &G.arcnum);
printf("请输入%d个顶点的值(<%d个字符):\n", G.vexnum, MAX_NAME);
for (i = 0; i < G.vexnum; ++i)
{
scanf("%s", G.vertices[i].data);
G.vertices[i].firstarc = NULL;
}
if (G.kind == 1 || G.kind == 3)
printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n");
else
printf("请顺序输入每条弧(边)的弧尾和弧头(以空格作为间隔):\n");
for (k = 0; k < G.arcnum; ++k)
{
if (G.kind == 1 || G.kind == 3) // 网
scanf("%d%s%s", &w, va, vb);
else // 图
scanf("%s%s", va, vb);
i = LocateVex(G, va); // 弧尾
j = LocateVex(G, vb); // 弧头
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = j;
if (G.kind == 1 || G.kind == 3) {
// 网
p->info = (int *)malloc(sizeof(int));
*(p->info) = w;
}
else
p->info = NULL; // 图
p->nextarc = G.vertices[i].firstarc; // 插在表头
G.vertices[i].firstarc = p;
if (G.kind >= 2){
// 无向图或网,产生第二个表结点
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = i;
if (G.kind == 3){
// 无向网
p->info = (int*)malloc(sizeof(int));
*(p->info) = w;
}
else
p->info = NULL; // 无向图
p->nextarc = G.vertices[j].firstarc; // 插在表头
G.vertices[j].firstarc = p;
}
}
return OK;
}
Boolean visited[MAX_VERTEX_NUM];
void(*VisitFunc)(char* v);
void Display(ALGraph G){
// 输出图的邻接表G
int i;
ArcNode *p;
switch (G.kind)
{
case DG: printf("有向图\n");
break;
case DN: printf("有向网\n");
break;
case AG: printf("无向图\n");
break;
case AN: printf("无向网\n");
}
printf("%d个顶点:\n", G.vexnum);
for (i = 0; i < G.vexnum; ++i)
printf("%s ", G.vertices[i].data);
printf("\n%d条弧(边):\n", G.arcnum);
for (i = 0; i < G.vexnum; i++){
p = G.vertices[i].firstarc;
while (p){
if (G.kind <= 1) {
// 有向
printf("%s→%s ", G.vertices[i].data, G.vertices[p->adjvex].data);
if (G.kind == DN) // 网
printf(":%d ", *(p->info));
}
else{
// 无向(避免输出两次)
if (i < p->adjvex) {
printf("%s-%s ", G.vertices[i].data, G.vertices[p->adjvex].data);
if (G.kind == AN) /* 网 */
printf(":%d ", *(p->info));
}
}
p = p->nextarc;
}
printf("\n");
}
}
void FindInDegree(ALGraph G, int indegree[])
{
/* 求顶点的入度,算法7.13调用 */
int i;
ArcNode *p;
for (i = 0; i < G.vexnum; i++)
indegree[i] = 0; /* 赋初值 */
for (i = 0; i < G.vexnum; i++)
{
p = G.vertices[i].firstarc;
while (p)
{
indegree[p->adjvex]++;
p = p->nextarc;
}
}
}
typedef int SElemType;
/* ----------------------------------- 栈的顺序存储表示 -----------------------------------*/
#define STACK_INIT_SIZE 10
#define STACKINCREMENT 2
typedef struct SqStack{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
Status InitStack(SqStack &S){
S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
if (!S.base)
exit(OVERFLOW);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status StackEmpty(SqStack S){
if (S.top == S.base)
return TRUE;
else
return FALSE;
}
Status Push(SqStack &S, SElemType e){
if (S.top - S.base >= S.stacksize) {
S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if (!S.base)
exit(OVERFLOW);
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*(S.top)++ = e;
return OK;
}
Status Pop(SqStack &S, SElemType &e){
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
if (S.top == S.base)
return ERROR;
e = *--S.top;
return OK;
}
//算法7.13
int ve[MAX_VERTEX_NUM];
Status TopologicalOrder(ALGraph G, SqStack &T){
int j, k, count, indegree[MAX_VERTEX_NUM];
SqStack S;
ArcNode *p;
FindInDegree(G, indegree);
InitStack(S);
for (j = 0; j < G.vexnum; ++j)
if (!indegree[j])
Push(S, j);
InitStack(T);
count = 0;
for (j = 0; j < G.vexnum; ++j)
ve[j] = 0;
while (!StackEmpty(S)){
Pop(S, j);
Push(T, j);
++count;
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
if (--indegree[k] == 0)
Push(S, k);
if (ve[j] + *(p->info) > ve[k])
ve[k] = ve[j] + *(p->info);
}
}
if (count < G.vexnum)
{
printf("此有向网有回路\n");
return ERROR;
}
else
return OK;
}
//算法7.14
Status CriticalPath(ALGraph G){
int vl[MAX_VERTEX_NUM];
SqStack T;
int i, j, k, ee, el;
ArcNode *p;
char dut, tag;
if (!TopologicalOrder(G, T)) // 产生有向环
return ERROR;
j = ve[0];
for (i = 1; i < G.vexnum; i++) //j=Max(ve[]) 完成点的值
if (ve[i] > j)
j = ve[i];
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)
{
k = p->adjvex;
dut = *(p->info); // dut<j,k>
if (vl[k] - dut < vl[j])
vl[j] = vl[k] - dut;
}
printf(" j k dut ee el tag\n");
for (j = 0; j < G.vexnum; ++j)//求ee,el和关键活动
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
dut = *(p->info);
ee = ve[j];
el = vl[k] - dut;
tag = (ee == el) ? '*' : ' ';
printf("%2d %2d %3d %3d %3d %c\n", j, k, dut, ee, el, tag); // 输出关键活动
}
printf("关键活动为:\n");
for (j = 0; j < G.vexnum; ++j)
for (p = G.vertices[j].firstarc; p; p = p->nextarc){
k = p->adjvex;
dut = *(p->info);
if (ve[j] == vl[k] - dut)
printf("%s→%s\n", G.vertices[j].data, G.vertices[k].data); // 输出关键活动
}
return OK;
}
int main(){
ALGraph h;
printf("请选择有向网\n");
CreateGraph(h);
Display(h);
CriticalPath(h);
}
测试样例
请选择有向网
请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): 1
请输入图的顶点数和边数(空格隔开): 6 8
请输入6个顶点的值(<5个字符):
v1 v2 v3 v4 v5 v6
请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):
2 v1 v3
3 v1 v2
2 v2 v4
3 v2 v5
1 v5 v6
2 v4 v6
4 v3 v4
3 v3 v6
运行结果
有向网
6个顶点:
v1 v2 v3 v4 v5 v6
8条弧(边):
v1→v2 :3 v1→v3 :2
v2→v5 :3 v2→v4 :2
v3→v6 :3 v3→v4 :4
v4→v6 :2
v5→v6 :1
j k dut ee el tag
0 1 3 0 1
0 2 2 0 0 *
1 4 3 3 4
1 3 2 3 4
2 5 3 2 5
2 3 4 2 2 *
3 5 2 6 6 *
4 5 1 6 7
关键活动为:
v1→v3
v3→v4
v4→v6