二 图 之 拓扑排序算法、关键路径

1、拓扑排序---主要解决一个工程能否顺序进行

 拓扑排序的意义?

举个例子先:一个软件专业的学生学习一系列的课程,其中一些课程必须再学完它的基础的先修课程才能开始。如:在《程序设计基础》和《离散数学》学完之前就不能开始学习《数据结构》。这些先决条件定义了课程之间的领先(优先)关系。这个关系可以用有向图更清楚地表示。图中顶点表示课程,有向边表示先决条件。若课程i是课程j的先决条件,则图中有弧。若要对这个图中的顶点所表示的课程进行拓扑排序的话,那么排序后得到的序列,必须是按照先后关系进行排序,具有领先关系的课程必然排在以它为基础的课程之前,若上例中的《程序设计基础》和《离散数学》必须排在《数据结构》之前。进行了拓扑排序之后的序列,称之为拓扑序列。


对AOV网进行拓扑排序的基本思想是:

从AOV网中选择一个入度为0的顶端输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止

//拓扑排序----无环图的应用(数据结构--邻接表)
//结构代码
typedef struct EdgeNode//边表结点
{
	int adjvex;//邻接点域,存储对应的下标
	int weight;//用于存储权值,对于非网图可以不需要
	struct EdgeNode *next;//链域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode//顶点表结点
{
	int in;//顶点入度
	int data;//存储顶点信息
	EdgeNode *firstedge;//边表头指针
}VertexNode, AdjList[MAXVEX];
typedef struct
{
	AdjList adjlist;
	int numVertexes, numEdges;//图中当前顶点数和边数
}graphAdjList, *GraphAdjList;
//在算法中还需要辅助的数据结构—-栈,用来处理存储过程中入度为0的顶点
//拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路,则返回ERROR
Status TopologicalSort(GraphAdjList GL)
{
	EdgeNode *e;
	int i, k, gettop;
	int top = 0;//用于栈指针下标
	int count = 0;//用于统计输出顶点的个数
	int *stack;//建栈存储入度为零的顶点
	stack = (int *)malloc(GL->numVertexes * sizeof(int));
	for (i = 0; i < GL.numVertexes; i++)
		if (GL->numVertexes; i++)
			stack[++top] = i;//将入度为0 的顶点入栈
	while(top != 0)
	{
	gettop = stack(top--);
	printf("%d->", GL->adjList[gettop].data);//打印此顶点
	count++;//统计输出的顶点数
	for (e = GL->adjList[gettop].firstedge; e; e = e->next)
	{//e=GL->adjList[gettop].firstedge表示e指向入度为0的gettop顶点的邻接点
		k = e->adjvex;//邻接点的下标赋给k
		if (!(--GL->adjList[k].in))//将将邻接点k的入度减1
			stack[++top] = k;//减1后,若为0则入栈,以便下次循环输出
	}//对此顶点弧表进行遍历
   }
	if (count < GL->numVertexes)//如果count小于顶点数,说明存在环
		return ERROR;
	else
		return OK;
}
2、关键路径---解决工程完成需要的最短时间

关键路径的定义:

路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径。

如何实现关键路径?

辨别关键活动就是要找活动最早开工时间e(i)等于活动最晚开工时间l(i),即(e(i)=l(i))的活动。为了求得e(i)和l(i),首先应求得事件的最早发生时间ve(j)和最迟发生时间vl(j)。如果活动由弧表示,其持续时间记为dut(),则有如下关系
e(i) = ve(j)
l(i) = vl(k) - dut()

解释:e(i)是表示活动最早开工时间,是针对弧来说的。但只有此弧的弧尾顶点j的事件发生了,它才可以开始,因此e(i) = ve(j)

l(i)表示的是活动最晚开工时间,但此活动再晚也不能等到k事件发生才开始,必须要在k事件发生之前发生,所以l(i) = vl(k) - dut()

求解ve(j)和vl(j)需分两个步进行:
1) 从ve(0)=0开始向前推进求得ve(j)
Ve(j) = Max{ve(i) + dut() };属于T,j=1,2...,n-1
其中T是所有以第j个顶点为头的弧的集合。

2) 从vl(n-1) = ve(n-1)起向后推进求得vl(j)
vl(i) = Min{vl(j) - dut(};属于S,i=n-2,...,0
其中,S是所有以第i个顶点为尾的弧的集合。
这两个递推公式的计算必须分别在拓扑有序和逆拓扑有序的前提先进行。也就是说,ve(j-1)必须在vj的所有前驱的最早发生时间求得之后才能确定,而vl(j-1)必须在Vj的所有后继的最迟发生时间求得之后才能确定。因此可以在拓扑排序的基础上计算ve(j-1)和vl(j-1)。


4、关键路径算法


首先定义全局变量:



其中stack2用来存储拓扑排序列,已便后面求关键路径时使用。





上述代码,除了加粗部分外,与前面的的拓扑排序算法没有什么不同的。


面来看关键路径的算法代码。



1、第6行,调用求拓扑序列的函数,求出etv,和栈stack的值如下,


2、当程序执行到第20行时,etv[1]=3,ltv[1]=7,表示v1这件事在第7天才开始,也可以保证整个工程的按期完成,v1也可以提前开始,但最早也只能在第3天开始。


3、所以就是判断ete与lte是否相等,相等意味着活动没有任何空闲,是关键活动时间,否则就不是,最后的关键路径如下。



个人总结

最长的一条路径就是关键路径,因为图中每个活动都是必须的,只有最长的工期完成后,项目才真正完成了,图中4+8+3+4+5+3=27 也就是v0-v2-v3-v4-v7-v8-v9 ,显然是最长的,所以为关键路径

从左边开始每个活动所需要最长的时间就是最早开始时间,如v1,只有v0指向它,那么最早开始时间就是3;相对于v3,v0->v2->v3
4+8=12,v0->v1->v3 3+5+8,两者比较,前者大,故12为最早开始时间,依次类推。

从右边倒推,可以求最迟开始时间,如v9为27,以v8为例,v8->v9 倒推 27-3=24 所以I最迟开始时间为24;v4为例,v4->v7->v8->v9 倒推27-3-5-4=15,v4->v6->v9 倒推 27-2-9=16,两者取最小的,所以v4的最迟开始时间为15



你可能感兴趣的:(数据结构)