C数据结构与算法-基础整理-图-09:拓扑排序和关键路径

0x01.相关概念

  1. AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity On Vertex Network)。
  2. 拓扑序列:G=(V,E) 是一个具有 n 个顶点的有向图,V 中的顶点序列 v_{1},v_{2},......,v_{n}, 满足若从顶点 v_{i} 到 v_{j} 有一条路径,则在顶点序列中顶点 v_{i} 必在顶点 v_{j} 之前。我们称这样的顶点序列为一个拓扑序列。
  3. 拓扑排序:对一个有向图构造拓扑序列的过程。
  4. AOE网:在一个表示活工程的有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续时间,这种有向图的边表示活动的网,我们称之为AOE网(Activity On Edge Network)。
  5. 关键路径:我们把路径上各个活动所持续时间之和称为路径长度,从源点到汇点具有最大长度的路径称为关键路径。
  6. 关键活动:在关键路径上的活动称为关键活动。

0x02.理解AOV网和AOE网

AOV网和AOE网都是表示工程的有向图,AOV网重在表示yo优先关系,AOE网重在表示持续时间。

AOV网:

AOE网:

0x03.拓扑排序算法

基本原理:

从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点,如果还存在,说明存在环,不能构成拓扑序列。

由于需要入度信息和删除操作,需要用到邻接表结构,但还需要入度信息,十字链表太过麻烦,最好的办法是手动输入一个顶点的入度,这样顶点的结构就由 in,data,firstedge,构成。需要用栈来保存入度为0的顶点。

代码:

int TopologicalAort(GraphAdjList GL)
{
	EdgeNode* e;
	int i, k, gettop;
	int top = -1;//栈指针
	int count = 0;//统计输出顶点个数
	int* stack;//此栈存储入度为0的顶点
	stack = (int*)malloc(GL.numv*sizeof(int));
	for (i = 0;i < GL.numv; i++)
	{
		if (GL.adjList[i].in == 0)
		{
			stack[++top] = i;
		}
	}
	while (top != -1)
	{
		gettop=stack[top--];//出栈
		printf("%c->", GL.adjList[gettop].data);//输出节点
		count++;
		for (e = GL.adjList[i].firstedge; e; e = e->next)//访问邻接边
		{
			k = e->adjvex;
			if (!(--GL.adjList[k].in))
			{
				stack[++top] = k;
			}
		}
	}
	if (count < GL.numv)
	{
		return false;
	}
	else
	{
		return true;
	}
}

0x04.关键路径

因为AOE网涉及时间的关系,首先需要了解几个概念。

  • 事件的最早发生时间 etv(earliest time of vertex):顶点 V_{k} 的最早发生时间。
  • 事件的最晚发生时间 ltv(lastest time of vertex):顶点 V_{k} 的最晚发生时间。
  • 活动的最早开工时间 ete(earliest time of edge):弧 a_{k} 的最早发生时间。
  • 活动的最晚开工时间 lte(lastest time of edge):弧  a_{k} 的最晚发生时间.。

首先我们需要求出事件的最早发生事件etv,这个步骤,可以在拓扑排序的同时求出。

为求关键路径改进的拓扑排序算法:

//首先需要定义几个全部变量
int* etv, * ltv;//求每个顶点的最早发生时间和最晚发生时间
int* stack2;//存储拓扑序列
int top2;
int TopologicalAort1(GraphAdjList GL)
{
	EdgeNode* e;
	int i, k, gettop;
	int top = -1;
	int count = 0;
	int* stack;
	stack = (int*)malloc(GL.numv * sizeof(int));
	for (i = 0; i < GL.numv; i++)
	{
		if (GL.adjList[i].in == 0)
		{
			stack[++top] = i;
		}
	}
	top2 = -1;
	etv = (int*)malloc(GL.numv * sizeof(int));
	for (i = 0; i < GL.numv; i++)//初始化事件的最早发生时间
	{
		etv[i] = 0;
	}
	stack2= (int*)malloc(GL.numv * sizeof(int));
	while (top != -1)
	{
		gettop = stack[top--];
		count++;
		stack[++top2] = gettop;//存入拓扑序列
		for (e = GL.adjList[gettop].firstedge; e; e = e->next)
		{
			k = e->adjvex;
			if (!(--GL.adjList[k].in))
			{
				stack[++top] = k;
			}
			if ((etv[gettop] + e->weight) > etv[k])//求出etv数组
			{
				etv[k] = etv[gettop] + e->weight;
			}
		}
	}
	if (count < GL.numv)
	{
		return false;
	}
	else
	{
		return true;
	}
}

这个改进的目的就在于求出etv数组,求etv数组的方法就是找到这个顶点的起始点,然后求出起始点到这个顶点的最长路径。

当 k\neq 0 时,etv[k]=max\left \{ etv[i]+ len<V_{i},V_{k}>\right \} 。例如:假如顶点V3有<V1,V3>,<V2,V3>,两条弧,那么etv[3]=max{etv[2]+len<V2,V3>,etv[1]+len<V1,V3>}。

关键路径算法:

void CrticalPath(GraphAdjList GL)
{
	EdgeNode* e;
	int i, gettop, k, j;
	TopologicalAort1(GL);
	int ete, lte;//活动的最早发生时间和最晚发生时间
	ltv= (int*)malloc(GL.numv * sizeof(int));
	for (i = 0; i < GL.numv; i++)//初始化ltv数组
	{
		ltv[i] = etv[GL.numv-1];//全部设置为etv中的最大值
	}
	while (top2 != -1)
	{
		gettop = stack2[top2--];
		for (e = GL.adjList[gettop].firstedge; e; e = e->next)
		{
			k = e->adjvex;
			if (ltv[k] - e->weight < ltv[gettop])//求出ltv数组
			{
				ltv[gettop] = ltv[k] - e->weight;
			}
		}
	}
	for (j = 0; j < GL.numv; j++)//对每个顶点的弧表遍历
	{
		for (e = GL.adjList[j].firstedge; e; e = e->next)
		{
			k = e->adjvex;
			ete = etv[j];
			lte = ltv[k] - e->weight;
			if (ete == lte)//活动的最早开工时间等于最晚开工时间,是关键路径
			{
				printf("(%c->%c),weight=%d", GL.adjList[j].data, GL.adjList[k].data, e->weight);
			}
		}
	}
}

算法原理分析:

在算法的前半部分,利用已产生的拓扑序列的栈,计算出了 ltv 数组,这个最晚开工时间应该从后向前推导,当 k=n-1 时,ltv[k]=etv[k],因为最后一个去完成的活动肯定是最后一个活动,别无它选,如果不是最后一个活动,那么[ltv[k]=min\left \{ ltv[j]-len<v_{k},v_{j}] \right \},例如:假如一个顶点V7有<V7,V8>,<V7,V9>两条弧,那么ltv[7]应该等于min{ltv[8]-len<V7,V8>(这条弧的长),ltv[9]-len<V7,V9>。

在确定最短路径的时候,出现了ete与lte,这两个概念都是针对于弧来说的,ete,弧的最早开工时间,应该是等于这条弧的弧尾的最早发生时间,lte,弧的最晚开工时间,应该是等于弧头的最晚开始时间-这条弧的权值。

如果ete等于lte就说明这条弧是关键路径上的弧,因为如果一条弧的最早开始时间和最晚开始时间不相等,就说明,中间有空闲,还可以去完成其它的活动,那么这就不是关键路径了,关键路径是整个网中路径最长的,从开始到结束,始终都是没有空闲的。

本章结束。

发布了50 篇原创文章 · 获赞 35 · 访问量 1314

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/104405995