数据结构(八)图

数据结构(八)图

1.图的概念

1.1图的定义:

图是一个由顶点集合V和一个弧集R构成的数据结构。

ADT Graph { ​ 数据对象V:V是具有相同特性的数据元素的集合,称为顶点集。 ​ 数据关系R: ​ R = {VR} ​ VR = {<v,w>|v,wV且P(v,w),<v,w>表示从v到w的弧,

谓词P(v,w)定义了<v,w>的意义或信息 }

1.2 图的重要术语

(1)无向图:在一个图中,如果任意两个顶点构成的偶对(v,w)∈E 是无序的,即顶点之间的连线是没有方向的,则称该图为无向图。

(2)有向图:在一个图中,如果任意两个顶点构成的偶对(v,w)∈E 是有序的,即顶 点之间的连线是有方向的,则称该图为有向图。

(3)无向完全图:在一个无向图中,如果任意两顶点都有一条直接边相连接,则称该图 为无向完全图。在一个含有 n 个顶点的无向完全图中,有 n(n-1)/2 条边。

(4)有向完全图:在一个有向图中,如果任意两顶点之间都有方向互为相反的两条弧相 连接,则称该图为有向完全图。在一个含有 n 个顶点的有向完全图中,有 n(n-1)条边。

(5)稠密图、稀疏图:若一个图接近完全图,称为稠密图;称边数很少(e<nlogn)的图为稀疏图。

(6)顶点的度、入度、出度: 顶点的度(degree)是指依附于某顶点 v 的边数,通常记为 TD (v)。 在有向图中,要区别顶点的入度与出度的概念。顶点 v 的入度是指以顶点为终点的弧的 数目,记为 ID (v);顶点 v 出度是指以顶点 v 为始点的弧的数目,记为 OD (v)。 TD (v)=ID (v)+OD (v)。 可以证明,对于具有 n 个顶点、e 条边的图,顶点 vi的度 TD (vi)与顶点的个数以及边的数目满足关系:

(7)边的权、网图:与边有关的数据信息称为权(weight)。在实际应用中,权值可以有 某种含义。边上带权的图称为网图或网络(network)。如果边是有方向的带权图,则就是一 个有向网图。

(8)路径、路径长度:顶点 vp到顶点 vq之间的路径(path)是指顶点序列 vp,vi1,vi2, …, vim,vq.。其中,(vp,vi1),(vi1,vi2),…,(vim,.vq)分别为图中的边。 路径上边的数目称为路径长度。

(9)简单路径、简单回路:序列中顶点不重复出现的路径称为简单路径。除第一个顶点 与后一个顶点之外,其他顶点不重复出现的回路称为简单回路,或者简单环。

(10)子图:对于图 G=(V,E),G’=(V’,E’),若存在 V’是 V 的子集 ,E’是 E 的子 集,则称图 G’是 G 的一个子图。

(11)连通图、连通分量:在无向图中,如果从一个顶点 vi到另一个顶点 vj(i≠j)有路径, 则称顶点 vi和 vj是连通的。如果图中任意两顶点都是连通的,则称该图是连通图。无向图的 极大连通子图称为连通分量。

(12)强连通图、强连通分量:对于有向图来说,若图中任意一对顶点 vi和 vj(i≠j)均有 从一个顶点 vi到另一个顶点 vj有路径,也有从 vj到 vi的路径,则称该有向图是强连通图。有 向图的极大强连通子图称为强连通分量。

(13)生成树:所谓连通图 G 的生成树,是 G 的包含其全部 n 个顶点的一个极小连通子 图。它必定包含且仅包含 G 的 n-1 条边。在生成树中添加任意一条属于原图中的边必定会产 生回路,因为新添加的边使其所依附的两个顶点之间有了第二条路径。若生成树中减少任意 一条边,则必然成为非连通的。

(14)生成森林:在非连通图中,由每个连通分量都可得到一个极小连通子图,即一棵 生成树。这些连通分量的生成树就组成了一个非连通图的生成森林。

2.图的存储及基本操作
2.1 邻接矩阵:

所谓邻接矩阵存储结构,就是用一维数组存储图中顶点的信息,用矩阵表示图中各顶点 之间的邻接关系。

从图的邻接矩阵存储方法容易看出,这种表示具有以下特点

(1)无向图的邻接矩阵一定是一个对称矩阵。因此,在具体存放邻接矩阵时只需存放上 (或下)三角矩阵的元素即可。

(2)对于无向图,邻接矩阵的第 i (或第 i 列)非零元素(或非∞元素)的个数正好是第 i 个顶点的度 TD(vi)。

(3)对于有向图,邻接矩阵的第 i (或第 i 列)非零元素(或非∞元素)的个数正好是第 i 个顶点的出度 OD(vi)(或入度 ID(vi))。

(4)用邻接矩阵方法存储图,很容易确定图中任意两个顶点之间是否有边相连;但是 ,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大, 这是用邻接矩阵存储图的局限性。

在用邻接矩阵存储图时,除了用一个二维数组存储用于表示顶点间相邻关系的邻接矩阵 外,还需用一个一维数组来存储顶点信息,另外还有图的顶点数和边数。故可

将其形式描述 如下:

#define MAX_VERTEX_NUM 20            //大顶点数设为 20 
typedef char VertexType;               //顶点类型设为字符型 
typedef int VRType;                    //边的权值设为整型 
typedef struct {     
    VertexType vexs[MAX_VERTEX_NUM];     //顶点表      
    VRType edges[MAX_VERTEX_NUM][ MAX_VERTEX_NUM];  //邻接矩阵,即边表 
    int vexnum,arcnum;                  //顶点数和边数 
}MGragh;                                //MGragh是邻接矩阵存储的图类型 
2.2 邻接表

邻接表(Adjacency List)是图的一种顺序存储与链式存储结合的存储方法。邻接表表示法类似于树的孩子链表表示法。就是对于图 G 中的每个顶点 vi,将所有邻接于 vi的顶点 vj链成 一个单链表,这个单链表就称为顶点 vi 的邻接表,再将所有点的邻接表表头放到数组中,就构成了图的邻接表。

邻接表表示的形式描述如下:

#define MAX_VERTEX_NUM  20         //大顶点数为 20 
typedef struct ArcNode{                //边表结点     
    int    adjvex;                    //邻接点域     
    struct ArcNode *nextarc;           //指向下一个邻接点的指针域                           
                       //若要表示边上信息,则应增加一个数据域info }ArcNode; typedef struct VNode{ //顶点表结点 VertexType data; //顶点域 ArcNode *firstarc; //边表头指针 }VNode, AdjList[MAX_VERTEX_NUM]; //AdjList 是邻接表类型 typedef struct{ AdjList adjlist; //邻接表 int vexnum,arcnum; //顶点数和边数 }ALGraph; //ALGraph是以邻接表方式存储的图类型

从图的邻接表存储方法容易看出,这种表示具有以下特点

(1)若无向图中有 n 个顶点、e 条边,则它的邻接表需 n 个头结点和 2e 个表结点。显 然,在边稀疏(e<<n(n-1)/2)的情况下,用邻接表表示图比邻接矩阵节省存储空间,当和边相关 的信息较多时更是如此;

(2)在无向图的邻接表中,顶点 vi的度恰为第 i 个链表中的结点数

(3)而在有向图中,第 i 个链表中的结点个数只是顶点 vi的出度,为求入度,必须遍历整个邻接表。在所有链表中其邻接点域的值为 i 的结点的个数是顶点 vi的入度。 有时,为了便于确定顶点的入度或以顶点 vi为头的弧,可以建立一个有向图的逆邻接表, 即对每个顶点 vi 建立一个链接以 vi为头的弧的链表。在建立邻接表或逆邻接表时,若输入的顶点信息即为顶点的编号,则建立邻接表的复杂度为 O(n+e),否则,需要通过查找才能得到顶点在图中位置,则时间复杂度为 O(n·e);

(4)在邻接表上容易找到任一顶点的第一个邻接点和下一个邻接点,但要判定任意两个 顶点(vi 和 vj)之间是否有边或弧相连,则需搜索第 i 个或第 j 个链表,因此,不及邻接矩阵方便。

3 图的遍历

图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次。图的 遍历操作和树的遍历操作功能相似。图的遍历是图的一种基本操作,图的许多其它操作都是 建立在遍历操作的基础之上。图的遍历通常有深度优先搜索和广度优先搜索两种方式。

3.1 深度优先搜索

深度优先搜索(Depth_Fisrst Search)遍历类似于树的先根遍历,是树的先根遍历的推广。 ​ 假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某个顶点发 v 出发, 访问此顶点,然后依次从 v 的未被访问的邻接点出发深度优先遍历图,直至图中所有和 v 有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的 顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

为了在遍历过程中便于区分顶点是否已被访问,需附设访问标志数组 visited[0:n-1], ,其初值为 FALSE ,一旦某个顶点被访问,则其相应的分量置为 TRUE。

从图的某一点 v 出发,递归地进行深度优先遍历的过程算法如下。

void DFSTraverse (Graph G) {        //深度优先遍历图 G 
    for (v=0; v<G.vexnum; ++v)    
        visited[v] = FALSE;         //访问标志数组初始化 
    for (v=0; v<G.vexnum; ++v)    
        if (!visited[v]) DFS(G,v);   //对尚未访问的顶点调用 DFS 
} 
void DFS(Graph G,int v ) {        //从第 v 个顶点出发递归地深度优先遍历图 G 
    visited[v]=TRUE;Visit(v);        //访问第 v 个顶点 
    for (w=FisrtAdjVex(G,v);w>=0; w=NextAdjVex(G,v,w)) 
        if (!visited[w]) DFS(G,w);   //对 v 的尚未访问的邻接顶点 w 递归调用 DFS 
} 

分析上述算法,在遍历时,对图中每个顶点至多调用一次 DFS 函数,因为一旦某个顶点 被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程

时间复杂度取决于所采用的存储结构。当用二维数组表示邻接矩阵 图的存储结构时,查找每个顶点的邻接点所需时间为 O(n2) ,其中 n 为图中顶点数。而当以 邻接表作图的存储结构时,找邻接点所需时间为 O(e),其中 e 为无向图中边的数或有向图中 弧的数。由此,当以邻接表作存储结构时,深度优先搜索遍历图的时间复杂度为 O(n+e) 。

3.2 广度优先搜索

广度优先搜索(Breadth_First Search) 遍历类似于树的按层次遍历的过程。

假设从图中某顶点 v 出发,在访问了 v 之后依次访问 v 的各个未曾访问过的邻接点,然 后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被 访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图 中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图 中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程中以 v 为起始点,由近 至远,依次访问和 v 有路径相通且路径长度为 1,2,…的顶点。

广度优先搜索和深度优先搜索类似,在遍历的过程中也需要一个访问标志数组。并且, 为了顺次访问路径长度为 2、 3、…的顶点,需附设队列以存储已被访问的路径长度为 1、 2、… 的顶点。

从图的某一点 v 出发,递归地进行广度优先遍历的过程算法如下。

void BFSTraverse (Graph G) {         //按广度优先非递归遍历图 G,使用辅助队列 Q 
    for (v=0; v<G.vexnum; ++v)   
        visited[v] = FALSE;          //访问标志数组初始化  
    for (v=0; v<G.vexnum; ++v)               
        if (!visited[v])  BFS(G, v);   //对尚未访问的顶点调用 BFS 
} 
void BFS (Graph G,int v) { 
    InitQueue(Q);                    //置空的辅助队列 Q 
    visited[v]=TRUE; Visit(v);        //访问 v 
    EnQueue(Q,v);                    //v入队列  
    while (!QueueEmpty(Q)) { 
        DeQueue(Q,u);                //队头元素出队并置为u 
        for(w=FistAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)) if (!visited[w]){ visited[w]=TRUE; Visit(w); EnQueue(Q,w); //u尚未访问的邻接顶点 w 入队列 Q 
      }
  }
}

  分析上述算法,每个顶点至多进一次队列。遍历图的过程实质是通过边或弧找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,两者不同之处仅仅在于对顶点访问的顺序不同。

4. 图的基本应用
4.1 最小生成树

4.1.1 最小生成树的基本概念

连通图的一次遍历所经过的边的集合及图中所有顶点的集合就构成了该图的一棵生成树,对连通图的不同遍历,就可能得到不同的生成树。如果无向连通图是一个网,那么它的所有生成树中必有一棵边的权值总和最小的生成树,我们称这棵生成树为最小生成树,简称为最小生成树

由生成树的定义可知,无向连通图的生成树不是唯一的。

对于有 n 个顶点的无向连通图,无论其生成树的形态如何,所 有生成树中都有且仅有 n-1 条边。

4.1.2 Prim 算法

假设 G=(V,E)为一网图,其中 V 为网图中所有顶点的集合,E 为网图中所有带权边的集合。设置两个新的集合 U 和 T,其中集合 U 用于存放 G 的最小生成树中的顶点,集合 T 存放 G 的最小生成树中的边。令集合 U 的初值为 U={u1}(假设构造最小生成树时,从顶点 u1 出发),集合 T 的初值为 T={}。

Prim 算法的思想是:从所有 u∈U,v∈V-U 的边中,选取具有最小权值的边(u,v),将 顶点 v 加入集合 U 中,将边(u,v)加入集合 T 中,如此不断重复,直到 U=V 时,最小生成树构造完毕,这时集合 T 中包含了最小生成树的所有边。

Prim算法可用下述过程描述,其中用 wuv表示顶点 u 与顶点 v 边上的权值。

(1)U={u1},T={};

(2)while (U≠V)do

  (u,v)=min{wuv;u∈U,v∈V-U }

  T=T+{(u,v)}

  U=U+{v}

(3)结束。

Prim算法的时间复杂度为 O(n2),与网中的边数无关,因此适用于求边稠密的网的小生成树。

4.1.3 Kruskal 算法

Kruskal 算法是一种按照网中边的权值递增的顺序构造最小生成树的方法。其基本思想 是:设无向连通网为 G=(V,E),令 G 的小生成树为 T,其初态为 T=(V,{}),即开 始时,小生成树 T 由图 G 中的 n 个顶点构成,顶点之间没有一条边,这样 T 中各顶点各自构成一个连通分量。然后,按照边的权值由小到大的顺序,考察 G 的边集 E 中的各条边。若 被考察的边的两个顶点属于 T 的两个不同的连通分量,则将此边作为小生成树的边加入到 T 中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分 量,则舍去此边,以免造成回路,如此下去,当 T 中的连通分量个数为 1 时,此连通分量便 为 G 的一棵小生成树。

Kruskal 算法需对 e 条边按权值进行排序,时间复杂度为 O(eloge)(e 为网中边的数目),因 此适用于求边稀疏的网的小生成树。

4.2 最短路径

1.从一个源点到其它各点的短路径

单源点的短路径问题:给定带权有向图 G=(V,E)和源点 v∈V,求从 v 到 G 中其 余各顶点的短路径。

(1)求从源点到其余各点的短路径的算法的基本思想: 依短路径的长度递增的次序求得各条路径。

①假设图中所示为从源点到其余各点之间的短路径,则在这些路径中,必然存在一条长度短者。其中,从源点到顶点 v 的短路径是所有短路径中长度短者。

②路径长度短的短路径的特点: 在这条路径上,必定只含一条弧,并且这条弧的权值小。

③下一条路径长度次短的短路径的特点: 它只可能有两种情况:或者是直接从源点到该点(只含一条弧);或者是,从源点经过顶点 v1,再到达该顶点(由两条弧组成)。

④再下一条路径长度次短的短路径的特点: 它可能有三种情况:或者是,直接从源点到该点(只含一条弧);或者是,从源点经过顶点 v1,再到达该顶点(由两条弧组成);或者是,从源点经过顶点 v2,再到达该顶点。

⑤其余短路径的特点: 它或者是直接从源点到该点(只含一条弧);或者是,从源点经过已求得短路径的顶点, 再到达该顶点。

(2)迪杰斯特拉(Dijkstra)算法

迪杰斯特拉提出的一个按路径长度递增的次序产生短路径的算法。

该算法的基本思想是:设置两个顶点的集合 S 和 T=V-S,集合 S 中存放已找到短路 径的顶点,集合 T 存放当前还未找到短路径的顶点。初始状态时,集合 S 中只包含源点 v0, 然后不断从集合 T 中选取到顶点 v0路径长度短的顶点 u 加入到集合 S 中,集合 S 每加入一 个新的顶点 u,都要修改顶点 v0到集合 T 中剩余顶点的短路径长度值,集合 T 中各顶点新 的短路径长度值为原来的短路径长度值与顶点 u 的短路径长度值加上 u 到该顶点的路 径长度值中的较小值。此过程不断重复,直到集合 T 的顶点全部加入到 S 中为止。

2.每一对顶点之间的短路径

解决这个问题的一个办法是:每次以一个顶点为源点,重复招待迪杰斯特拉算法 n 次。 这样,便可求得每一结顶点的短路径。总的执行时间为 O(n3)

弗洛伊德(Floyd)算法:

弗洛伊德算法仍从图的带权邻接矩阵 cost 出发,其基本思想是:

假设求从顶点vi到vj的短路径。如果从vi到vj有弧,则从vi到vj存在一条长度为arcs i 的路径,该路径不一定是短路径,尚需进行 n 次试探。首先考虑路径(vi, v0, vj)是否存在 (即判别弧(vi, v0)和(v0, vj)是否存在)。如果存在,则比较(vi, vj)和(vi, v0, vj)的路径长度取长度较短者为从 vi 到 vj 的中间顶点的序号不大于 0 的短路径。假如在路径上再增加 一个顶点 v1,也就是说,如果 (vi, …, v1)和(v1, …, vj)分别是当前找到的中间顶点的序号 不大于 0 的短路径,那么(vi, …, v1, … , vj)就有可能是从 vi到 vj的中间顶点的序号不大于 1 的短路径。将它和已经得到的从 vi到 vj中间顶点序号不大于 0 的短路径相比较,从中 选出中间顶点的序号不大于 1 的短路径之后,再增加一个顶点 v2,继续进行试探。依次类 推。在一般情况下,若(vi, …, vk)和(vk, …, vj)分别是从 vi到 vk和从 vk到 vj的中间顶点的 序号不大于 k-1 的短路径,则将(vi, …, vk, …, vj)和已经得到的从 vi到 vj且中间顶点序号 不大于 k-1 的短路径相比较,其长度较短者便是从 vi到 vj的中间顶点的序号不大于 k 的 短路径。这样,在经过 n 次比较后,后求得的必是从 vi到 vj的短路径。按此方法,可以 同时求得各对顶点间的短路径。

4.3 拓扑排序

1.AOV 网

所有的工程或者某种流程可以分为若干个小的工程或阶段,这些小的工程或阶段就称为活动。若以图中的顶点来表示活动,有向边表示活动之间的优先关系,则这样活动在顶点上 的有向图称为 AOV 网。在 AOV 网中,若从顶点 i 到顶点 j 之间存在一条有向路径,称顶点 i 是顶点 j 的前驱,或者称顶点 j 是顶点 i 的后继。若<i,j>是图中的弧,则称顶点 i 是顶点 j 的 直接前驱,顶点 j 是顶点 i 的直接后驱。

2.拓扑排序

AOV 网所代表的一项工程中活动的集合显然是一个偏序集合。为了保证该项工程得以顺利完成,必须保证 AOV 网中不出现回路;否则,意味着某项活动应以自身作为能否开展的先决条件,这是荒谬的。 测试 AOV 网是否具有回路(即是否是一个有向无环图)的方法,就是在 AOV 网的偏序集合下构造一个线性序列,该线性序列具有以下性质:

① 在 AOV 网中,若顶点 i 优先于顶点 j ,则在线性序列中顶点 i 仍然优先于顶点 j;

② 对于网中原来没有优先关系的顶点 i 与顶点 j,在线性序列中也建立一个先后关系, 或者顶点 i 优先于顶点 j ,或者顶点 j 优先于 i。

满足这样性质的线性序列称为拓扑有序序列。构造拓扑序列的过程称为拓扑排序。也可 以说拓扑排序就是由某个集合上的一个偏序得到该集合上的一个全序的操作。

若某个 AOV 网中所有顶点都在它的拓扑序列中,则说明该 AOV 网不会存在回路,这时 的拓扑序列集合是 AOV 网中所有活动的一个全序集合。显然,对于任何一项工程中各个活动 的安排,必须按拓扑有序序列中的顺序进行才是可行的。

3.拓扑排序算法

对 AOV 网进行拓扑排序的方法和步骤是:

①从 AOV 网中选择一个没有前驱的顶点(该顶点的入度为 0)并且输出它;

②从网中删去该顶点,并且删去从该顶点发出的全部有向边;

③重复上述两步,直到剩余的网中不再存在没有前驱的顶点为止。

这样操作的结果有两种:一种是网中全部顶点都被输出,这说明网中不存在有向回路; 另一种就是网中顶点未被全部输出,剩余的顶点均不前驱顶点,这说明网中存在有向回路。

对一个具有 n 个顶点、e 条边的网来说,整个算法的时间复杂度为 O(e+n)。

4.4 关键路径

1.AOE 网

若在带权的有向图中,以顶点表示事件,以有向边表示活动,边上的权值表示活动的开 销(如该活动持续的时间),则此带权的有向图称为 AOE 网。

AOE网具有以下两个性质:

①只有在某顶点所代表的事件发生后,从该顶点出发的各有向边所代表的活动才能开始。

② 只有在进入一某顶点的各有向边所代表的活动都已经结束,该顶点所代表的事件才能 发生。

2.关键路径

由于 AOE 网中的某些活动能够同时进行,故完成整个工程所必须花费的时间应该为源点 到终点的大路径长度(这里的路径长度是指该路径上的各个活动所需时间之和)。具有大 路径长度的路径称为关键路径。关键路径上的活动称为关键活动。关键路径长度是整个工程 所需的短工期。这就是说,要缩短整个工期,必须加快关键活动的进度。

利用 AOE 网进行工程管理时要需解决的主要问题是:

①计算完成整个工程的短路径。

②确定关键路径,以找出哪些活动是影响工程进度的关键。

3.关键路径的确定

为了在 AOE 网中找出关键路径,需要定义几个参量,并且说明其计算方法。

(1)事件的早发生时间 ve[k]

ve[k]是指从源点到顶点的大路径长度代表的时间。这个时间决定了所有从顶点发出的 有向边所代表的活动能够开工的早时间。根据 AOE 网的性质,只有进入 vk 的所有活动< vj,vk>都结束时,vk 代表的事件才能发生;而活动< vj, vk>的早结束时间为 ve[j]+dut(< vj, vk>)。所以计算 vk 发生的早时间的方法如下:

ve[l]=0

ve[k]=Max{ve[j]+dut(< vj, vk>)} < vj, vk>∈p[k]

其中,p[k]表示所有到达 vk的有向边的集合;dut(< vj, vk>)为有向边< vj,vk>上的权值。

(2)事件的迟发生时间 vl[k]

vl[k]是指在不推迟整个工期的前提下,事件 vk 允许的晚发生时间。设有向边< vk,vj> 代表从 vk 出发的活动,为了不拖延整个工期,vk 发生的迟时间必须保证不推迟从事件 vk 出 发的所有活动< vk,vj>的终点 vj 的迟时间 vl[j]。vl[k] 的计算方法如下:

vl[n]=ve[n]

vl[k]=Min{vl[j]-dut(< vk,vj>)} < vk, vj>∈s[k]

其中,s[k]为所有从 vk 发出的有向边的集合。

(3)活动 ai 的早开始时间 e[i]

若活动 ai 是由弧<vk,vj>表示,根据 AOE 网的性质,只有事件 vk 发生了,活动 ai 才能开 始。也就是说,活动 ai 的早开始时间应等于事件 vk的早发生时间。

因此,有: e[i]=ve[k]

(4)活动 ai 的晚开始时间 l[i]

活动 ai 的晚开始时间指,在不推迟整个工程完成日期的前提下, 必须开始的晚时间。 若 由弧< vk,vj>>表示,则 ai 的晚开始时间要保证事件 vj 的迟发生时间不拖后。

因此, 应该有: l[i]=vl[j]-dut(<vk,vj>)

根据每个活动的早开始时间 e[i]和晚开始时间 l[i]就可判定该活动是否为关键活动, 也就是那些 l[i]=e[i]的活动就是关键活动,而那些 l[i]>e[i]的活动则不是关键活动,l[i]-e[i]的 值为活动的时间余量。关键活动确定之后,关键活动所在的路径就是关键路径。

由上述方法得到求关键路径的算法步骤为:

(1)输入 e 条弧<j,k>,建立 AOE-网的存储结构;

(2)从源点v0出发,令ve[0]=0,按拓扑有序求其余各顶点的早发生时间vei。 如果得到的拓扑有序序列中顶点个数小于网中顶点数 n,则说明网中存在环,不能求关键路 径,算法终止;否则执行步骤(3)。

(3)从汇点 vn 出发,令 vl[n-1]=ve[n-1],按逆拓扑有序求其余各顶点的迟发生时间 vl[i] (n-2≥i≥2);

(4)根据各顶点的 ve 和 vl 值,求每条弧 s 的早开始时间 e(s)和迟开始时间 1(s)。 若某条弧满足条件 e(s)=l(s),则为关键活动。

转载于:https://www.cnblogs.com/ST-2017/p/10468012.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值