7.6有向无环图应用之关键路径

7.6有向无环图应用之关键路径

关键路径
有向图在工程计划和经营管理中有着广泛的应用。通常用有向图来表示工程 计划时有两种方法:

  • (1)用顶点表示活动,用有向弧表示活动间的优先关系,即上节所讨论的 AOV 网。
  • (2)用顶点表示事件,用弧表示活动,弧的权值表示活动所需要的时间。

把用第二种方法构造的有向无环图叫做边表示活动的网(Activity On Edge Network),简称 AOE-网

AOE-网在工程计划和管理中很有用。在研究实际问题时,人们通常关心的是:

  • 哪些活动是影响工程进度的关键活动?
  • 至少需要多长时间能完成整个工程?

在 AOE 网中存在惟一的、入度为 0 的顶点,叫做源点;存在惟一的、出度为 0 的顶点,叫做汇点。从源点到汇点的最长路径的长度即为完成整个工程任务所 需的时间,该路径叫做关键路径。关键路径上的活动叫做关键活动。这些活动中 的任意一项活动未能按期完成,则整个工程的完成时间就要推迟。相反,如果能够加快关键活动的进度,则整个工程可以提前完成。

例如,在下图所示的 AOE-网中,共有 9 个事件,分别对应顶点 v0, v1, v2, …,v7, v8。其中 v0为源点,表示整个工程可以开始。事件 v4表示 a4,a5已经完成,a7,a8 可以开始。v8为汇点,表示整个工程结束。v0到v8的最长路径(关键路径)有两 条:(v0,v1,v4,v7,v8)或(v0,v1,v4,v6,v8),长度均为 18。关键活动为
在这里插入图片描述
(a1,a4,a7,a10)或(a1,a2,a8,a11)。 关键活动 a1计划 6 天完成,如果 a1 提前 2 天完成,则整个工程也可以提前 2 天完成。

在讨论关键路径算法之前,首先给出几个重要的定义:

  • (1)事件 vi的最早发生时间 ve(i):从源点到顶点 vi的最长路径的长度,叫做 事件 vi的最早发生时间。
    求 ve(i) 的值可从源点开始,按拓扑顺序向汇点递推:
ve(0)=0ve(i)=Max{ ve(k)+dut(<k,i>} 
<k,i>∈T,1≤i≤n-1; 

其中,T 为所有以 i 为头的弧<k,i>的集合,dut(<k,i>)表示与弧<k,i>对 应的活动的持续时间。

  • (2)事件 vi的最晚发生时间 vl(i):在保证汇点按其最早发生时间发生这一前提下,求事件 vi的最晚发生时间。
    在求出 ve(i)的基础上, 可从汇点开始,按逆拓扑顺序向源点递推,求出 vl(i):
vl(n-1)=ve(n-1)vl(i)=Min{vl(k)+dut(<i,k>} 
<i,k>∈S,0≤i≤n-2; 

其中,S 为所有以 i 为尾的弧<i,k>的集合,dut(<i,k>)表示与弧<i,k>对应的 活动的持续时间。

  • (3)活动 ai的最早开始时间 e(i):如果活动 ai 对应的弧为<j,k>,则 e(i)等于从源点到顶点 j 的最长路径的长度,即:e(i)=ve(j)
  • (4)活动 ai的最晚开始时间 l(i):如果活动 ai对应的弧为<j,k>,其持续时间为 dut(<j,k>)则有:l(i)=vl(k)- dut(<j,k>) 即在保证事件vk的最晚发生时间为vl(k)的前提下,活动ai的最晚开始时间为l(i)
  • (5)活动 ai的松弛时间(时间余量):ai的最晚开始时间与 ai的最早开始时间之差:l(i)- e(i)。 显然,松弛时间(时间余量)为 0 的活动为关键活动

求关键路径的基本步骤如下:
①对图中顶点进行拓扑排序,在排序过程中按拓扑序列求出每个事件的最早 发生时间 ve(i);
②按逆拓扑序列求每个事件的最晚发生时间 vl(i);
③求出每个活动 ai的最早开始时间 e(i)和最晚发生时间 l(i); ④找出 e(i)=l(i) 的活动 ai,即为关键活动。

下面首先修改上一节的拓扑排序算法,以便同时求出每个事件的最早发生时 间 ve(i):
算法思想

  • (1) 首先求出各顶点的入度,并将入度为 0 的顶点入栈 S;
  • (2) 将各顶点的最早发生时间 ve[i]初始化为 0;
  • (3) 只要栈 S 不空,则重复下面处理:
    ①将栈顶顶点 j 出栈并压入栈 T(生成逆拓扑序列);
    ②将顶点 j 的每一个邻接点 k 的入度减 1,如果顶点 k 的入度变为 0,则将 顶点 k 入栈;
    ③根据顶点 j 的最早发生时间 ve[j]和弧<j, k>的权值,更新顶点 k 的最早发 生时间 ve[k]。

算法描述】 修改后的拓扑排序算法

int  ve[MAX_VERTEX_NUM];    /*每个顶点的最早发生时间*/ 
int TopoOrder(AdjList G,Stack * T)  /* G 为有向网,T 为返回拓扑序列的栈,S 为存放入度为 0 的顶点的栈*/ 
{ 
	int count,i,j,k; 
	ArcNode *p; 
	int indegree[MAX_VERTEX_NUM];  /*各顶点入度数组*/ 
	Stack  S;   
	InitStack(T);  
	InitStack(&S);   /*初始化栈 T,  S*/   
	FindID(G,  indegree);  /*求各个顶点的入度*/   
	for(i=0;i<G.vexnum;i++)     
		if(indegree[i]==0)   
			Push(&S,i);   
	count=0;   
	for(i=0;i<G.vexnum;i++)     
			ve[i]=0;   /*初始化最早发生时间*/ 
	while(!IsEmpty(&S))    
	{  
		Pop(&S,&j); 
		Push(T,j); 
		count++; 
		p=G.vertex[j].firstarc; 
		while(p!=NULL)        
		{ 
			k=p->adjvex;   
			if(--indegree[k]==0)  
				Push(&S,k);   /*若顶点的入度减为 0,则入栈*/      
			if(ve[j]+p->Info.weight>ve[k])  
				ve[k]=ve[j]+p->Info.weight;      
			p=p->nextarc;        
		}  /*while*/     
	} /*while*/    
	if (count<G.vexnum)  
		return(Error);    
	else 
		return(Ok); 
} 

有了每个事件的最早发生时间,就可以求出每个事件的最迟发生时间,进一 步可求出每个活动的最早开始时间和最晚开始时间,最后就可以求出关键路径了。

求关键路径的算法实现如下:
算法思想

  • (1) 首先调用修改后的拓扑排序算法,求出每个事件的最早发生时间和逆拓扑 序列栈 T;
  • (2) 将各顶点的最晚发生时间 vl[i]初始化为汇点的最早发生时间;
  • (3) 只要栈 T 不空,则重复下面处理: ①将栈顶顶点 j 出栈; ②对于顶点 j 的每一个邻接点 k,根据顶点 k 的最晚发生时间 vl[k]和弧<j, k> 的权值,更新顶点 j 的最晚发生时间 vl[j]。
  • (4) 扫描每一条弧,计算其最早发生时间 ei 和最晚发生时间 li,如果 ei 等于 li 则输出该边。

算法描述】 关键路径算法

int CriticalPath(AdjList G) 
{ 
	ArcNode  *p;  
	int  i,j,k,dut,ei,li;  
	char tag; int  vl[MAX_VERTEX_NUM];    /*每个顶点的最迟发生时间*/ 
	Stack T;   
	if(!TopoOrder(G, &T))  
		return(Error);   
	for(i=0; i<G.vexnum; i++)  
		vl[i]=ve[G.vexnum-1];     /* 将各顶点事件的最迟发生时间初始化为 汇点的最早发生时间 */ 
	while(!IsEmpty(&T))   /*按逆拓扑顺序求各顶点的 vl 值*/    
	{  
		Pop(&T,&j); 
		p=G.vertex[j].firstarc; 
		while(p!=NULL)         
		{ 
			k=p->adjvex;  
			dut=p->weight;         
			if(vl[k]-dut<vl[j])  
				vl[j]= vl[k]-dut;           
			p=p->nextarc;       
		} /* while */    
	} /* while*/ 
	for(j=0;j<G.vexnum;j++)   /*求 ei,li 和关键活动*/    
	{ 
		p=G.vertex[j].firstarc;    
		while(p!=NULL)       
		{ 
			k=p->Adjvex; 
			dut=p->Info.weight;         
			ei=ve[j];
			li=vl[k]-dut;  
			tag = (ei==li) ? '*' : ' ' ;    /*标记并输出关键活动*/ 
			printf("%c,%c,%d,%d,%d,%c\n",G.vertex[j].data,G.vertex[k].data,dut,ei,li,ta g);           
			p=p->nextarc;       
		} /*while*/    
	} /* for */ 
	return(Ok); 
} /*CriticalPath*/ 

算法的时间复杂度为 O(n+e)。用该算法求上图中 AOE-网的关键路径,结果 如下所示。
在这里插入图片描述
例如,对下图所示的 AOE 网计算关键路径过程如下:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是求解AOE网关键路径的C语言代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_VERTEX_NUM 10 #define MAX_ARC_NUM 100 typedef struct ArcNode { int adjvex; int weight; struct ArcNode *nextarc; } ArcNode; typedef struct VNode { int data; ArcNode *firstarc; } VNode; typedef struct { VNode vertices[MAX_VERTEX_NUM]; int vexnum, arcnum; } AGraph; int *etv, *ltv; int *stack2; int top2 = -1; void CreateGraph(AGraph *G) { int i, j, k, w; ArcNode *p; printf("请输入顶点数和弧数:"); scanf("%d%d", &G->vexnum, &G->arcnum); printf("请输入%d个顶点:", G->vexnum); for (i = 0; i < G->vexnum; i++) { scanf("%d", &G->vertices[i].data); G->vertices[i].firstarc = NULL; } printf("请输入%d条弧的起点、终点和权值:\n", G->arcnum); for (k = 0; k < G->arcnum; k++) { scanf("%d%d%d", &i, &j, &w); p = (ArcNode *) malloc(sizeof(ArcNode)); p->adjvex = j; p->weight = w; p->nextarc = G->vertices[i].firstarc; G->vertices[i].firstarc = p; } } void TopologicalSort(AGraph *G) { int i, k, gettop; int top = -1; int *stack; stack = (int *) malloc(G->vexnum * sizeof(int)); for (i = 0; i < G->vexnum; i++) { if (G->vertices[i].firstarc == NULL) { stack[++top] = i; } } top2 = -1; etv = (int *) malloc(G->vexnum * sizeof(int)); for (i = 0; i < G->vexnum; i++) { etv[i] = 0; } stack2 = (int *) malloc(G->vexnum * sizeof(int)); while (top != -1) { gettop = stack[top--]; stack2[++top2] = gettop; for (ArcNode *p = G->vertices[gettop].firstarc; p != NULL; p = p->nextarc) { k = p->adjvex; if (--indegree[k] == 0) { stack[++top] = k; } if (etv[gettop] + p->weight > etv[k]) { etv[k] = etv[gettop] + p->weight; } } } if (top2 != G->vexnum - 1) { printf("该AOE网存在环,无法进行拓扑排序!\n"); exit(0); } } void CriticalPath(AGraph *G) { int i, j, k; int ete, lte; ArcNode *p; TopologicalSort(G); ltv = (int *) malloc(G->vexnum * sizeof(int)); for (i = 0; i < G->vexnum; i++) { ltv[i] = etv[G->vexnum - 1]; } while (top2 != -1) { gettop = stack2[top2--]; for (p = G->vertices[gettop].firstarc; p != NULL; p = p->nextarc) { k = p->adjvex; if (ltv[k] - p->weight < ltv[gettop]) { ltv[gettop] = ltv[k] - p->weight; } } } for (j = 0; j < G->vexnum; j++) { for (p = G->vertices[j].firstarc; p != NULL; p = p->nextarc) { k = p->adjvex; ete = etv[j]; lte = ltv[k] - p->weight; if (ete == lte) { printf("<%d, %d> length: %d, ", j, k, p->weight); } } } } int main() { AGraph *G; G = (AGraph *) malloc(sizeof(AGraph)); CreateGraph(G); CriticalPath(G); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值