活动顶点与活动边的问题07

图的活动顶点排序问题的引入

一般的复杂工程都可以划分为多个工序/步骤/子工程,这些工序有的可独立进行,但大多数和其他工序关联,即某工序的进行,要等到其他一些工序的完成才能开始。只有按一定的顺序完成了这些工序,才能正确完成整个工程。下面来看一些实际的例子。

  1. 装配顺序问题

Brown 教授早晨起床,他必须先穿好某些衣服,才能再穿其他衣服(如先穿袜子后穿鞋),其他一些衣服则可以按任意次序穿戴(如袜子和裤子),若把各衣物穿戴活动当做顶点,先后顺序做有向边,则可以画出图(a)“穿衣装配”顺序。图(b)是将(a)做了一个整理,在水平线方向形成一个顶点序列,使得图中所有有向边均从左指向右。“装配顺序问题”即转换为有向图的顶点排序问题。
在这里插入图片描述

  1. 先修课问题

假定有同学看了 MOOC (大型开放式网络课程,Massive Open Online Course)中“机械学习”这门课程的介绍,觉得有兴趣准备学习,但不知道自己的基础知识是否够用,这个时候,就要去查一下这门课程的先修课程有哪些。对于相关专业的课程,要按照相应的知识体系先后顺序来进行学习,有些课程是基础课,他们可独立于其他课程先学习,有些课要有先修课程做基础。学习计划的制定,就需要确定选修课程的顺序。

我们可以把课程抽象为一个顶点,而课程学习的先后关系或制约关系即是连接顶点之间的有向边。这样,课程及相关关系即可用一个有向图来表示,确定各课程学习的先后顺序问题,即转换为对有向图的顶点进行排序的问题。由于是基于拓扑结构中定点的排序,因此称为是拓扑排序。
在这里插入图片描述
有向图都能够被拓扑排序吗?

讨论:若顶点之间存在互相依赖的关系的话,就无法确定二者的先后顺序。在有向图中,这种情况被描述为存在环路。

因此,一个有向图能被拓扑排序的充要条件就是它是一个有向无环图(Directed Acyclic Graph,DAG)。这样的图的特点是不能有环路存在。
在这里插入图片描述

  1. 源代码分析问题

我们平常所使用的主流编译器,都具有多源代码文件支持。例如,把一些函数定义放在相应的文件中,要使用到这些函数时,需要包含定义这些函数的文件(如 C),或引用类所在的名字空间(如 Java),或将这个文件作为单元引用(如 Object Pascal)。
支持多源代码文件的编译器,需要在编译某个源代码文件之前先编译这个源代码所引用到的文件。例如,有两个头文件 A.h 和 B.h ,内容如下:

//头文件 A.h
int next(void)
{
	...
 } 
 int last(void)
 {
 	...
 }
 int news(int i)
 {
 	...
 }
 //头文件 B.h
 int reset(void)
 {
 	...
  } 
  源文件 D.cpp 里面用到了 A.h 和 B.h 中定义的函数,内容如下:
  int main()
  {
  	...
  	i=reset();
  	for(j=1; j<4; j++)
  	{
  		printf("%d\t",next());
  		printf("%d\t",last());
  		printf("%d\t",news(i+j));
	  }
	  return 0;
   } 

在编译 D.cpp 时,如果 A.h 和 B.h 中的文件未被预先编译,编译器将无法识别 reset 等函数,以及对 reset 的调用参数列表是否正确等。这时就需要先分析 D.cpp 引用了哪些文件,这些文件是否又引用了其他文件,并优先编译处于引用列表顶端的文件,并以此类推。
要分析计算源代码依赖关系,可以先把所有源代码文件看成一个个的顶点,一个顶点(源代码文件)如果引用了另一个顶点,就增加一条从当前顶点到被引用顶点的出边,当增加完所有顶点的出边后,正常情况下这些顶点就形成了一个有向无环图,如果出现了环,说明源代码文件中产生了错误的循环引用,这样会导致无法编译。
在这里插入图片描述
问题:如何避免同一个文件被 include 多次?

AOV网与拓扑排序——活动顶点排序问题

  1. AOV 网模型与拓扑排序
    在这里插入图片描述
  2. 拓扑排序定义

在这里插入图片描述
【思考与讨论】拓扑排序的结果唯一吗?
讨论:以存储于数组中的若干整数排序问题为例。
若数组中所有整数均不同,则各元素之间的大小关系是确定的,即这个序列是满足全序关系的。对于拥有全序关系的结构,在其线性化(排序)之后的结果必然是唯一的。
如数组中存在相同的整数,相同值的元素之间的关系无法确定,因此它们在最终的排序结果中的出现顺序可以是任意的。
在这里插入图片描述

  1. 拓扑排序算法

要对有向无环图进行拓扑排序,入手点在哪里?
1)算法分析

我们以选课问题为例,最先选择学习的课程,应该是不需要有先修课程的,如可以首先选择“计算机导伦” C1 或 “高等数学” C6 做开始的顶点,若先选 C1,则后续可以选 C3 和 C6 。观察被选的顶点,其特征是入度为 0 。选择 C3 的原因是去掉 C1 后其入度由原先的 1 变为了 0,逐步排序的过程见图
在这里插入图片描述
由于此种拓扑排序方法每一步总是输出当前无前驱(即入度为零)的顶点,因此也被称为无前驱的顶点优先的拓扑排序算法。

2) 算法描述

(1)顶部伪代码

1 从有向图中选取一个没有前趋的顶点v,并输出之;
2 从有向图中删去顶点v,及其所有出边;
3 重复上述两步,直至顶点全部处理完毕,或者找不到无前趋的顶点为止。后一种情况说明有向图中存在环。

(2)细化步骤一

NonPreFirstTopSort(G)
{//优先输出无前趋的顶点
  	while(G中有入度为0的顶点)
  	{
       	从G中选择一个入度为0的顶点v且输出之;
      	从G中删去v及其所有出边;
  	}
  	if(输出的顶点数目<|V(G)|)
    	//若此条件不成立,则表示所有顶点均已输出,排序成功。
     	Error("G中存在有向环,排序失败!")}

(3)细化步骤二
为了实现拓扑排序的算法,对给定的有向图可采用邻接表作为它的存储结构。为便于考察每个顶点的入度,可保存各顶点当前的入度在数组 indegree[] 中。为避免每次选入度为 0 的顶点时扫描整个存储空间,可设一个栈或队列暂存所有入度为零的顶点,在开始排序前,扫描对应的存储空间,将入度为零的顶点均入栈(队)。以后每次选入度为零的顶点时,只需做出栈(队)操作即可。删去一个顶点时,所有它的直接后继顶点入度均减 1,表示相应的有向边也被删除掉。
① 把邻接表中所有入度为零的顶点进栈;
② 在栈不空时:
退栈并输出栈顶的顶点j;
在邻接表的第j个单链表中,查找顶点为j的所有直接后继顶点k,并将k的入度减1。若顶点k的入度为零,则顶点k进栈;
若栈空时输出的顶点个数不是n,则有向图中有环路,否则拓扑排序完毕。

3)数据结构描述
在这里插入图片描述
(1)邻接表结构
(2)入度数组 int indegree[]——存放每个顶点的入度
拓扑排序序列数组 int v[]——存放拓扑排序结果(顶点编号)
顶点栈 SeqStack S——存放入度为 0 的顶点

4)算法实现

/*=======================================
函数功能:无前趋的顶点优先的拓扑排序
函数输入:邻接表G、(拓扑排序序列数组v[])
函数输出:TURE——G无回路, FALSE——G有回路
        拓扑排序序列数组v[]
==========================================*/
int TopologicalSort(AL_Graph G,int v[])
{ 
    int i,k,count=0;
    int indegree[VERTEX_NUM];		//入度数组 
    SeqStack S; 					//顶点栈                    
    AL_AdjNode *p; 				//邻接结点指针
    FindInDegree(G,indegree); 		//求顶点求入度   
    initialize_SqStack(&S); 			//初始化栈  
    //入度为0的顶点进栈S           
    for(i=0;i<G.VexNum; ++i)       
//	for(i=G.VexNum-1;i>=0; i--) 		//从indegree相反方向扫描,得到另一组拓扑排序结果      
       if(!indegree[i]) Push_SqStack(&S,i); 
    //S栈非空
    while(!StackEmpty_SqStack(&S))     
    {  Pop_SqStack(&S,&i);                
       v[count]=i;  ++count; 			//记录度为0顶点编号

    //扫描邻接链表,入度为0的顶点进栈
       for(p=G.VexList[i].link; p; p=p->next) 
       {   k=p->adjvex;          
            if(!(--indegree[k])) Push_SqStack(&S,k); 
}
     }
     if(count<G.VexNum)  return FALSE;
     else  return TRUE;
}
#include <stdio.h>
#include <stdlib.h>
#include "SqStack.h"			//顺序栈操作函数
#include "AdjList.h"			//邻接表建立函数
/*===========================================
函数功能:求图的入度
函数输入:邻接表G、(入度数组indegree[])
函数输出:入度数组indegree[]
============================================*/
void FindInDegree(AL_Graph G, int indegree[])
{   
    int i;
    AL_AdjNode *p;
    for(i=0;i<G.VexNum;i++)     
       indegree[i]=0;            
    for(i=0;i<G.VexNum;i++)     
    {  
       p=G.VexList[i].link; 
       while(p)                  
       {   
            indegree[p->adjvex]++;  
            p=p->next;              
       }
    }
}

int main()
{
	AL_Graph G;
	int a[VERTEX_NUM];
	G=Create_AdjList();
	TopologicalSort(G,a);
	printf("拓扑排序序列:\n");
	for (int i=0; i<VERTEX_NUM; i++)
		printf("c%d ",a[i]+1);
	printf("\n ");
	
return 0;
} 

5)复杂度分析

对有 n 个顶点和 e 条弧的有向图而言,建立求各顶点的入度的时间复杂度为 O(e);建立零入度顶点栈的时间复杂度为 O(n);在拓扑排序过程中,若有向图无环,则每个顶点进一次栈,入度减 1 的操作在 while 语句中总共执行 e 次,所以,总的时间复杂度为 O(n+e) 。

6)其他拓扑排序算法

拓扑排序方法除了前面介绍的无前驱的顶点优先的方法外,还有无后继的顶点优先拓扑排序方法和利用深度优先遍历对DAG拓扑排序。

利用深度优先遍历对DAG拓扑排序:只要将深度优先遍历算法 DFSTraverse 中对 DFS 的调用改为对 DFSTopSort 的调用,即可求得拓扑序列 T。

AOE网与关键路径——活动边最长问题

AOE(Activity On Edge)网是带权有向图,其中顶点表示事件,边表示活动,权值表示活动的持续事件。
关键路径法(Critical Path Method,CPM)是基于 AOE 网络的最长路径分析方法。

  1. 问题的背景

用拓扑排序主要是为解决一个工程能否顺序进行的问题,除此之外,往往同时还关心工程的进度,如我们想知道完成整项工程至少需要多少时间?哪些活动是影响工程进度的关键?

如在拓扑排序中给出的选课问题,通过拓扑排序可以得知各门课程的先后顺序,若还想知道至少在多长时间内学完这些课程时,则要加上一个参数——每门课的学时数,这样,问题就变成了对一个流程图获得最短时间的问题,即完成整个工程至少需要多少时间。
在这里插入图片描述
为方便问题的描述,将图(a)拓扑变形为图(b)。AOV 网是顶点表示活动的网,它只描述活动之间的制约关系,课程学时这个参数,是活动的持续时间,可以看成是活动的权值,新加入信息后,应该在图中如何表示出来呢?

回顾图的概念,权值得标记一般是在图的边上,这样比较直观易于理解,因此,在本问题中,AOV 网中表示活动的顶点,就要转变成“活动边”,这一步是容易实现的。接下来的问题是,这样的“活动边”的关联顶点,又应该是什么呢?

从“活动边”实质的含义看,其关联顶点应该分别是这个活动的开始和结束,如图所示。
在这里插入图片描述
我们把一个“活动”的顶点到边的转换分析清楚后,下一步的工作应该是将这些“活动边”联系起来。我们可以通过一个活动的结束,就是另一个活动的开始,这样的线索来链接它们,如从逻辑关系上看,“C1 结束” 点即是 “C3 开始” 点,故这两个点可以合并为一个顶点,为方便描述,给它一个顶点的编号 v2,。每个顶点的编号只要不同即可。
在这里插入图片描述
其中 “源点” 和 “汇点” 分别表示整个工程的开始和结束。因为中间有些活动边依然跟顶点相仿,如 C2、C4、C5 ,我们可以根据边与顶点的逻辑关系和连接情形,将之分开成多条边。
在这里插入图片描述
在这里插入图片描述

  1. AOE 网的概念

在这里插入图片描述

  1. 关键活动计算方法分析

关键路径(Critical Path)与关键活动:
在 AOE 网络中,有些活动顺序进行,有些活动并行进行。从源点到各个顶点,以至从源点到汇点的有向路径可能不止一条。这些路径的长度也可能不同。完成不同路径的活动所需的时间虽然不同,但只有各条路径上的所有活动都完成了,整个工程才算完成。因此,完成整个工程所需的时间取决于从源点到汇点的最长路径长度,即在这条路径上所有活动的持续时间之和。这条路径长度最长的路径就叫关键路径。由于 AOE 网中的某些活动能够同时进行,故完成整个工程所必须花费的时间应该为源点到汇点的最大路径长度,我们称之为关键路径,关键路径长度是整个工程所需的最短工期。关键路径上的活动即为关键活动。

1)关键活动算法分析

我们由一个 AOE 网的实例来分析关键路径的求解方法。
某调研项目 10 项活动组成,根据活动明细表中各活动的制约关系,可以画出项目网格图,从源点到汇点,计算最长路径,观察顶点的情形,可以分为两类,一类是入度为 1,另一类是入度大于1 。
(1)入度为 1 的顶点:该顶点出边活动开始的时刻,是该点入边活动结束的时刻。如顶点 V2,活动 a3 开始是在 a1 结束的时间。
(2)入度大于 1 的顶点:该顶点出边活动开始的时刻,是该点所有入边活动结束的时刻。取所有入边路径中的最大值。

在这里插入图片描述
如顶点 V4,活动 a7是必须在其之前两条路径 a1、a3 和 a2、a5 都要结束之后才能开始。故 V4 开始的时刻,要在路径值 4 和 5 中选最大值 5 。按照各顶点事件的开始时间计算值,得到汇点的值为 20,它的含义为整个 AOE 网的最长路径。
在这里插入图片描述
【思考与讨论】仅知道最长路径值,能否确定关键事件?

最长路径值只是一个观察结果,仅从此表各点的信息中无法“计算”出关键时间对应的顶点。需进一步更多的信息来确定关键事件的特征数据。

从汇点到源点,逆推各事件的最迟开始时间,其中 ee[k] 表示事件可能发生的最早时间(Earliest Event),le[k] 表示事件可能发生的最晚时间(Latest Event)。
观察顶点的情形,仍然可以分为两类,一类是出度为 1,另一类是出度大于 1 。
(1)出度为 1 的顶点:该顶点出边活动最晚开始的时刻,是其弧头结点时间减去边的活动时间。如顶点 V6,活动 a10 最晚开始时刻=V7时间 -a10=20-6=14
(2)出度大于 1 的点:所有出边活动最晚开始的时刻,是各弧头结点时间减活动时间中最小的。

如顶点 v4,有两条出边 a7 和 a8,对应弧尾结点分别为 v5 和 v6,所以 v4 点对应的最晚开始时间有 7 和 12 两个值,若取值 12,则 v4 到汇点两条路径中,经过最长的那条路径 v4、v5、v7 到汇点的时间为 25,超过了前面推算出的最长路径;若取值 7,则其值为 20,与最长路径值一致,故出度大于 1 的点,最晚开始时刻要选最小值。
在这里插入图片描述
【结论】
源点 ->汇点:入度 > 1 的顶点 最早开始时刻取最大值。
汇点 ->源点:出度 > 1 的顶点 最晚开始时刻取最小值。

【思考与讨论】如何确定关键事件?
已经知道图的最长路径经过 v1、v2、v5、v7 各点,观察同一顶点的 ee[k] 和 le[k],发现二者的差为 0,而非最长路径上的点,其差值均不为 0,所以,这个“最晚和最早开始时间差值为 0”就可以做判断顶点是否为关键事件的条件了,如图。

这个差值的实际意义上看,最晚和最早开始时间,一致的情形表示此事件不能早也不能晚,只能在那一时刻开始工作;而不一致的情形,说明此事件可以开始的时间是介于二者中的任一时间都是可以的。

事件分析表
在这里插入图片描述
下面可以按同样的方法分析和确定关键活动,见下图,其中 ee[i] 为活动的最早开始时间,与对应事件相同; el[i] 为活动的最晚开始时间,是弧尾顶点最晚开始时间减去对应活动时间。el[i] 与 ee[i] 差值为 0 的活动即是关键活动 a1、a4、a9 。
在这里插入图片描述
2)关键路径的特点

(1)关键路径上的活动持续时间决定了项目的工期,关键路径上所有活动的持续时间总和就是项目的工期。
(2)关键路径上的任何一个活动都是关键活动,其中任何一个活动的延迟都会导致整个项目完工时间的延迟。
(3)关键路径上的耗时是可以完工的最短时间量,若缩短关键路径的总耗时,会缩短关键路径的总耗时,会缩短项目工期;反之,则会延长整个项目的总工期。但是如果在一定限度内缩短非关键路径上的各个活动所需要的时间,也不至于影响工程的完工时间。
(4)关键路径上活动是总时差最小的活动,改变其中某个活动的耗时,可能使关键路径发生变化。
(5)可以存在多条关键路径,它们各自的时间总量肯定相等,即可完工的总工期。

3)关键路径计算的一般性方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4. 求关键路径算法

1)伪代码描述
伪代码描述一
(1)对图进行拓扑排序,若不能进行拓扑排序则退出。
(2)按拓扑序列顺序,从前向后搜索寻找活动边,计算相应事件的最早开始时间。
(3)按拓扑序列顺序,从后向前搜索寻找活动边,计算相应事件的最迟开始时间。
(4)计算各活动的最早开始时间和最迟开始时间
(5)求出关键事件和关键活动

伪代码细化描述一
(1)对图进行拓扑排序,形成拓扑排序 S 与拓扑排序 T,若不能进行拓扑排序则退出。
(2)以拓扑排序 S 的次序,计算各个顶点(事件)最早发生的时刻。计算公式
ve(j)= Max{ve(i) + dut(<i,j>) }
(3)以逆拓扑排序 T 的次序,计算各个顶点(事件)最晚发生的时刻。计算公式
vl(i)= Min{vl(j) - dut(<i,j>) }
(4)计算每个活动<i, j>发生的最早时间 ee(i, j)和最晚时间 el(i, j)。

伪代码细化描述二
TopoSort函数:对图进行拓扑排序并求出事件的最早发生时间
(1)以邻接表作存储结构
(2)把邻接表中所有入度为0的顶点进栈S
(3)S栈非空时,栈顶元素Vi出栈,另外将Vi压栈至T;
(4)在邻接表中查找Vi的直接后继Vk,把Vk的入度减1,若Vk的入度为0则进栈S,
(5)算出vi的邻接点k的最早发生时间:
(6)重复上述步骤3至5,直至S栈空为止。
(7)若栈空时输出的顶点个数不是图的顶点数,则有向图有环;否则,拓扑排序完毕。

CriticalPath 函数:求关键路径和关键活动拓扑排序,排序逆序结果放在栈 T 中:
(1)初始化顶点事件的最迟发生时间 v1 为汇点的最早发生时间 ve。
(2)按 T 栈顺序求出每个顶点的最迟发生时间。
(3)依次遍历每一个活动。
活动<i,k>的最早开始时间 ee=顶点 i 的最早开始时间 ve[i]
活动<i,k>的最晚开始时间 el=顶点 k 的最迟开始时间 vl[k]-dut
若(ee==el)则<i, k> 为关键活动

2)数据结构设计
(1)图用邻接表表示,在邻接表的顶点结构中增加一个记录顶点入度的数据项 indegree,保存 AOV 网中每个顶点的入度值,在邻接点结构中增加一记录权值 weight 的数据项,保存边的权值。建立邻接表数据结构与函数在 AdjList.h 中。
在这里插入图片描述
(2)建立顺序栈 S 和 T,分别放置拓扑排序序列和拓扑排序逆序列,顺序栈数据结构与操作函数在 SqStack.h 中。

(3)事件最早发生时间数组 int ve[VERTEX_NUM];
事件最迟发生时间数组 int vl[VERTEX_NUM];

程序实现

#include <stdio.h> 
#include <stdlib.h>
#define TRUE 1
#define FALSE 0 
#include "AdjList.h" 
#include "SqStack.h" 

/*========================================================
函数功能:拓扑排序与逆排序、求各顶点事件的最早发生时间    
函数输入:图的邻接表、(顺序栈S)、(事件最早发生时间数组)
函数输出:顺序栈(内容为拓扑逆序列)、事件最早发生时间数组
================================================*/
void TopoSort(AL_Graph &G, SeqStack &T,int ve[])    
{    
    SeqStack S;    
    initialize_SqStack(&S); 
    AL_AdjNode *p;    
        
    int count=0;    
    int i; 
    int k;    
    int dut;  
	
    for(i=0;i<G.VexNum;i++)    
        G.VexList[i].indegree=0;		//结点的度初始化为0    
    //计算各个顶点的入度   
    for(i=0;i<G.VexNum;i++)    
    { 
        p=G.VexList[i].link;    
        while(p)    
        {    
            G.VexList[p->adjvex].indegree++;    
            p=p->next;    
        }    
    }
    //将入度为0的顶点入栈
    for(i=0;i<G.VexNum;i++)
    {
        //入度为0的顶点在VexList数组中的位置i进栈          
        if(G.VexList[i].indegree==0)    
             Push_SqStack(&S,i); 
    }
   //初始化顶点事件的最早发生时间为0
    for(i=0;i<G.VexNum;i++) ve[i]=0;    
    printf("拓扑排序序列:");
    while(S.top!=-1)    
    {    
        Pop_SqStack(&S, &i); //入度为0的元素出栈
        printf("%d ",G.VexList[i]); //输出入度为0的顶点
        //将S中的拓扑排序序列出栈后再依次进入T栈就得到了拓扑逆序列
        Push_SqStack(&T,i);
        count++;//顶点计数器加1
        p=G.VexList[i].link;//让p指向入度为0的顶点的第一个边表结点    
        while(p)    
        {    
            dut=p->weight;    
            k=p->adjvex;    
            //将入度为0的顶点的邻接点的入度减1  
            G.VexList[k].indegree--;  
            //入度减1后的顶点如果其入度为0,则将其入栈  
            if(G.VexList[k].indegree==0) Push_SqStack(&S,k);  
            //经过while循环,将顶点事件的所有邻接点的最早发生时间算出来
            if(ve[i]+dut>ve[k]) ve[k]=ve[i]+dut;    
            p=p->next;    
        }    
    }    
    printf("\n");
    if(count<G.VexNum)    
        printf("Network G has citcuits!\n");
}    
/*===============================
函数功能:求关键路径和关键活动    
函数输入:图的邻接表
函数输出:无
屏幕输出:关键路径和关键活动
==================================*/
void CriticalPath(AL_Graph &G)    
{    
    int i,j,k,dut;    
    int ee,el;    				//活动最早发生时间与最迟发生时间
    int ve[VERTEX_NUM]; 		//事件最早发生时间 
    int vl[VERTEX_NUM]; 		//事件最迟发生时间   
    AL_AdjNode *p;    
    SeqStack T;  
	
    initialize_SqStack(&T);    
    TopoSort(G,T,ve); 			//拓扑排序,排序逆序结果放在栈T中   
    //初始化顶点事件的最迟发生时间为汇点的最早发生时间
    for(i=0;i<G.VexNum;i++) vl[i]=ve[G.VexNum-1]; 
    //while循环,按T栈顺序求出每个顶点的最迟发生时间    
    while(T.top!=-1)    
    {
        //按栈T中事件的顺序,将各顶点事件的最迟发生时间循环算出
        for(Pop_SqStack(&T,&j),p=G.VexList[j].link; p ;  p=p->next)    
        {
            k=p->adjvex;    
            dut=p->weight;    
            if(vl[k]-dut<vl[j])    
                vl[j]=vl[k]-dut;		//算出顶点事件的最迟发生时间 
        }    
    } 
	
    printf("起点 终点 最早开始时间 最迟开始时间 关键活动\n");
    int totaltime=0;
    //依次遍历每一个活动<i,k>
    for(i=0;i<G.VexNum;i++)    
    {    
        for(p=G.VexList[i].link;p;p=p->next)    
        {    
            k=p->adjvex;    
            dut=p->weight;    
            ee=ve[i]; 			//活动<i,k>的最早开始时间    
            el=vl[k]-dut;			//活动<i,k>的最迟开始时间    
            //为配合测试样例中顶点编号从1开始,故输出顶点编号加1 
            printf("%4d %4d",i+1,k+1);
            printf("%12d %12d",ee,el);
            if(ee==el) 			//关键活动   
            {    
                    printf("  (%2d,%2d)",G.VexList[i].vertex+1,G.VexList[k].vertex+1);
                    totaltime+=dut;
            } 
            printf("\n");
        }    
    }
    printf("关键路径长度为:%d\n",totaltime);

}    
int main()    
{    
    AL_Graph G;  
    G=Create_AdjList();
    CriticalPath(G);    
    return 0;
}

活动顶点与活动边问题小结

AOV 网要解决的问题:第一,它里面有回环吗?即工程的流程是否合理;第二,如果该网没有回环,那么各个子过程的安排有怎样一些顺序。这两个问题可由拓扑排序的方法来解决。
AOE 网是顶点表示活动的网,它只描述活动之间的制约关系,而 AOE 网是用边表示活动的网,边上的权值表示活动持续的时间。用正推法和逆推法计算出各个活动的最早开始时间,最晚开始时间,最早完工时间和最迟完工时间,并计算出各个活动的时差;找出所有时差为零的活动所组成的路线,即为关键路径。

图结构小结
在这里插入图片描述

  1. 图的存储方法

相邻关系矩阵
相邻关系链表

  1. 图的遍历

深度优先:图的深度优先遍历类似与树的前序遍历。按访问顶点次序得到的序列称 DFS 序列。
广度优先:图的广度优先遍历类似与树的层次遍历。按访问顶点次序得到的序列称 BFS 序列。

  1. 图的应用

最小生成树:权最小的生成树(无向图)。相应算法有普里姆算法和克鲁斯卡尔算法。

最短路径(带权有向图)。单源最短路径问题:求从某个源点出发到其余各个顶点的最短路径;所有顶点对间最短路径问题:分别对图中不同顶点对转换为单源最短路径问题;相关算法为 Dijkstra 算法、Floyd 算法。

拓扑排序(有向无环图):将图中所有顶点排成一个线性序列,满足弧尾在弧头之前。

关键路径(带权有向图):边上的权表示完成该活动所需的时间。关键路径是图中最长的一条路径。

=====================================================

发布了26 篇原创文章 · 获赞 3 · 访问量 1461

猜你喜欢

转载自blog.csdn.net/herui7322/article/details/104457266