关键路径——CriticalPath算法

背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的。该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解。

关键路径:

即决定一项工程的完成时间的路径。

如下图所示,是一辆汽车的生产流程,其中外壳、发动机、轮子等的生产过程都是可以并行进行的,但是发送机生产需要的时间最长,而只有所有零部件生产完成才才能进行下一步,因此图中用红色加粗的那一条路径即为该工程的关键路径(即决定工程的实际完成时间的路径)。

CriticalPath算法理解:

此算法是在拓扑排序的基础上进行的,首先对AOE图完成拓扑排序,并将排序结果保存在栈中(代码中是栈stack2)。

在拓扑排序完成之后,计算各个事件(顶点)、活动(弧)的etv、ltv、ete、lte的值。

其中lte = ete的活动(弧)即为关键活动,关键活动连接成为关键路径。

代码如下:(有详细注释)(较容易理解)

/* 弧 edge */
typedef struct EdgeNode
{
	int adjvex;	// 弧指向的顶点位置(下标)
	struct EdgeNode *next;
}EdgeNode;

/* 顶点 node */
typedef struct VertexNode
{
	int in;		// 顶点入度
	int data;
	EdgeNode *firstedge;	// 第1个依附于该顶点的弧
}VertexNode, AdjList[MAXVEX];

/* 邻接表 */
typedef struct
{
	AdjList adjList;
	int numVertexes;
	int numEdges;
}graphAdjList, *GraphAdjList;

int *etv, *ltv;
int *stack2;		// 用于存储拓扑序列的栈
int top2;				// stack2的栈顶指针

/* 拓扑排序算法 */
/* 若GL无回路 则输出拓扑排序序列并返回OK 否则返回ERROR */
/* 拓扑排序仅改变这些顶点的排列顺序(这里是按顺序存放在stack2中) 而并不改变顶点之间的连接关系 */
Status TopologicalSort(GraphAdjList GL)
{
	EdgeNode *e;
	int i, k, gettop;
	int top = 0;		// 用于栈指针下标索引
	int count = 0;	// 用于统计输出顶点的个数
	int *stack;			// 用于存储入度为0的顶点(用于拓扑排序的栈)

	stack = (int *)malloc(GL->numVertexes * sizeof(int));

	for( i=0; i < GL->numVertexes; i++ )
	{
		if( 0 == GL->adjList[i].in )
		{
			/* 将入度为0的顶点下标入栈 */
			stack[++top] = i;
		}
	}


	top2 = 0;
	etv = (int *)malloc(GL->numVertexes*sizeof(int));
	/* 初始化etv都为0 */
	for( i=0; i < GL->numVertexes; i++ )
	{
		etv[i] = 0;
	}
	stack2 = (int *)malloc(GL->numVertexes*sizeof(int));

	/* 拓扑排序 */
	while( 0 != top )
	{
		/* 出栈 */
		gettop = stack[top--];
		/* 原本拓扑排序这里需要打印 */
		// printf("%d -> ", GL->adjList[gettop].data);
		/* 保存拓扑序列顺序 */
		stack2[++top2] = gettop;
		/* 记录拓扑排序输出顶点个数(用以判断是否存在环路) */
		count++;

		/* 取出一个入度为0的顶点后 以它为前驱的顶点入度-1 */
		for( e=GL->adjList[gettop].firstedge; e; e=e->next )
		{
			k = e->adjvex;
			/* 入度-1后若为0 则入栈 */
			if( !(--GL->adjList[k].in) )
			{
				stack[++top] = k;
			}

			/* 如果某一个顶点有多个入度 则该事件需要等待这几个活动都完成才能有效 */
			/* 故这里判断如果有更长的路径 则替代原本的etv */
			if( (etv[gettop]+e->weight) > etv[k] )
			{
				etv[k] = etv[gettop] + e->weight;
			}
		}
	}

	/* 如果count小于顶点数 说明存在环 */
	if( count < GL->numVertexes )
	{
		return ERROR;
	}
	else
	{
		return OK;
	}
}

/* 求关键路径 */
/* GL为有向图 输出GL的各项关键活动 */
void CriticalPath(GraphAdjList GL)
{
	EdgeNode *e;
	int i, gettop, k, j;
	int ete, lte;

	/* 调用改进后的拓扑排序 */
	/* 求出etv数组(事件最早发生时间)和stack2(拓扑序列) */
	TopologicalSort(GL);

	/* 初始化ltv(事件最晚发生时间)都为汇点的时间(ltv是由汇点开始倒推的) */
	ltv = (int *)malloc(GL->numVertexes*sizeof(int));
	for( i=0; i < GL->numVertexes; i++ )
	{
		/* 汇点的发生时间(汇点etv=ltv) */
		ltv[i] = etv[GL->numVertexes-1];
	}

	/* 从汇点倒推计算ltv */
	while( 0 != top2 )
	{
		/* 第一个出栈的是汇点 */
		gettop = stack2[top2--];

		/* 汇点无firstedge 故直接出栈下一个点 */
		/* 只有有出度的顶点 firstedge才不为空 */
		for( e=GL->adjList[gettop].firstedge; e; e=e->next )
		{
			k = e->adjvex;

			/* 如果某一个顶点有多个出度 则在计算ltv时需要选择通过这几条出度算得的ltv的最小值 */
			/* 为了因为如果没有选择其中的最小值 则计算出更小的ltv的那条出度的路径将导致汇点延时 */
			/* 故这里判断如果有更小的ltv结果 则替代原本的ltv */
			if( (ltv[k] - e->weight) < ltv[gettop] )
			{
				ltv[gettop] = ltv[k] - e->weight;
			}
		}
	}

	/* 通过etv和ltv 求ete和lte */
	for( j=0; j < GL->numVertexes; j++ )
	{
		for( e=GL->adjList[j].firstedge; e; e=e->next )
		{
			k = e->adjvex;
			/* 顶点(事件)的etv即它的出度的弧(活动)的ete */
			ete = etv[j];
			/* 弧(活动)的lte即它所指向的下一个顶点(事件)的ltv-弧的权值weight */
			lte = ltv[k] - e->weight;

			/* ete = lte的事件即为关键活动 连接成为关键路径 */
			if( ete == lte )
			{
				/* 输出关键路径 */
				printf("<v%d,v%d> length: %d , ", GL->adjList[j].data, GL->adjList[k].data, e->weight );
			}
		}
	}
}

 

——cloud over sky

 ——2020/3/12

 

 

 

 

posted @ 2020-03-13 00:57  Yu_tiann  阅读(422)  评论(0编辑  收藏  举报