本章主要掌握如下内容:
图的概念,图的存储及基本操作,邻接矩阵法,邻接表法,图的深度优先搜索和广度优先搜索,最小代价生成树,最短路径,拓扑排序,关键路径。
知识点分析
(一) 图的基本概念
1.图的定义及术语
图是比线性表和树形结构更为复杂的数据结构。在线性表中,数据元素之间有前驱、后继的关系;在树形结构中,数据元素之间有明显的层次关系,且元素可以和下一层的多个元素(即孩子结点)相关联,但只和一个上一层的结点相关联(父结点);在图结构中,数据元素之间的关系可以是任意的,图中任意两个元素之间都可能有关系,并且,图中没有严格意义上的开始结点。
1) 图的定义:图G={ V, { E } } ,是顶点和边的集合。V是一个无穷非空集合,由许多顶点组成,{E}是顶点之间关系的集合,当{E}= Φ时,说明顶点之间没有任何关系,各顶点之间没有边或者弧,是离散的,否则,说明图的顶点之间是有关联的。
图中的数据元素称为顶点(Vertex)。设v,u是两个顶点,如果两顶点之间有直接连接,无方向性则称(v,u)为边,有方向性则称<v,u>为弧。如果是v->u,则称v是弧尾,也称弧的初始点,u为弧头,也称弧的终端点,这样的图称为有向图(Digraph),否则称为无向图(Undigraph)。
例如:下图4-1中,G1是有向图,G2是无向图。
说明:数据结构课程中讨论的图,不考虑图的顶点到其自身的弧或者边,也不考虑顶点和顶点之间有多条边关联的情况。
2) 图中的边(弧)同图的顶点的关系
设用n表示图中顶点的数目,用e表示图中边或弧的数目,则
① 无向图G中边数目的取值范围:0<= e <= n(n-1)/2。有n(n-1)/2条边的无向图称为完全图。如:
② 有向图G中弧数目的取值范围:0<= e <= n(n-1)。有n(n-1)条弧的有向图称为有向完全图。如下图所示,有向图G共有4个顶点,12条弧。如图4-3所示:
③ 有很少边或者弧的图称为稀疏图,反之称为稠密图。
说明: “很少边”少到什么程度?“很多边”又多到什么程度?这并没有确切定义。一般认为e<nLogn,就认为该图为稀疏图。
3) 权:如果图的边或者弧上有同它相关的数,这些数称为权(Weight),用于表示从一个顶点到另一个顶点的距离或花费。带权的图称为网。
4) 子图(subGraph):假设有两个图G={V,{E}}和G’ = {V’,{E’}},如果满足V’是V的子集,并且E’也是E的子集,则称G’是G的子图。
子图说明的是两个图的关系:从一个图中除去i条边或弧(0<=i<=e),同时除去j个顶点(0<=j<=n)得到的新的图称为原图的子图。一个图可以有多个子图。
5) 度(degree):顶点v的度:和v相关联的边或弧的条数,用TD(v)表示。
①对无向图来说,顶点Vi的度是无向图中同该顶点相关联的边的数目,记为:TD(Vi)
②对有向图来说,顶点的度是有向图中同该顶点相关联的所有弧(有向边)的数目,记为TD(Vi)。由于弧有方向性,因此针对某顶点还分为出度OD(Vi)和入度ID(Vi),其中:
顶点的出度OD(Vi)――-从此顶点射出去的弧的个数;
顶点的入度ID(Vi)――-进入(指向)该顶点的弧的个数;
Vi顶点的度:TD(Vi)=OD(Vi)+ID(Vi)。
总结:如果顶点Vi的度记为TD(Vi),则图(包括有向图和无向图)的所有顶点的度的总和是该图的边或弧数的两倍。即:设e是图的边数(或弧数),则有:
e = ( TD(V1) + TD(V2) + … + TD(Vn) ) / 2 =
『经典例题解析』
1.图中有关路径的定义是( )。
A.由顶点和相邻顶点序偶构成的边所形成的序列 B.由不同顶点所形成的序列
C.由不同边所形成的序列 D.上述定义都不是
【答案】A。
【解析】略。
2.设无向图的顶点个数为n,则该图最多有( )条边。
A.n-1 B.n(n-1)/2 C. n(n+1)/2 D.0 E.n2
【答案】B。
【解析】无向图G中边数目的取值范围:0<= e <= n(n-1)/2。有n(n-1)/2条边的无向图称为完全图。
3.一个n个顶点的连通无向图,其边的个数至少为( )。
A.n-1 B.n C.n+1 D.nlogn;
【答案】A。
【解析】略。
4.要连通具有n个顶点的有向图,至少需要( )条边。
A.n-l B.n C.n+l D.2n
【答案】B。
【解析】略。
5.n个结点的完全有向图含有边的数目( )。
A.n*n B.n(n+1) C.n/2 D.n*(n-l)
【答案】D。
【解析】有向图G中弧数目的取值范围:0<= e <= n(n-1)。有n(n-1)条弧的有向图称为有向完全图。
2.图的连通性问题
1) 路径和回路
① 顶点到顶点的路径:如果顶点Vi和Vj之间有通路(可达),则Vi到Vj之间的所有顶点序列称为Vi到Vj的路径。如果G为有向图,则Vi到Vj的路径为有向路径。
② 路径长度:路径上边或弧的数目称为路径长度。如图4-5所示
V1到V6有多条路径:
1> (V1,V2,V3,V6),长度为3
2> (V1,V5,V6),长度为2
③ 回路和路径:如果一条路径上的第一个顶点和最后一个顶点相同,则称这条路径为回路或环路。如下例:(V1,V2,V3,V6,V5,V1)就是一个回路。路径上没有重复顶点的,称为简单路径。除去第一个顶点和最后一个顶点,其余顶点不重复出现的回路,称为简单回路或简单环。上例(V1,V2,V3,V6,V5,V1)是一个简单回路,而下图4-6中例子则是非简单
的回路。
2) 图的连通性
① 如果从顶点v到u有路径,则称为v和u是连通的。
② 连通图:对图G中任何两个顶点,如果是连通的,则称G为连通图。对有向图来说,如果每对顶点之间v到u,u到v都是连通的,则称该有向图为强连通图。
③ 连通分量和极大连通子图:虽然无向图G本身不连通,但是G一般都有极大连通子图(也是连通的),称为G的连通分量。有向图的极大强连通子图称为G的强连通分量,如下图4-7所示。(所谓“极大”,是指再向G的这个子图增加一个顶点,该子图就不再是连通图了。)
3)连通图的生成树问题
连通图中两顶点之间的路径可能有多条,如果保持顶点个数不变,而是通过删减边/弧的方法使得两个顶点之间只有一条路径,则生成的图称为原图的极小连通子图,又称为原图的生成树。如下图4-8。
说明:生成树实际上就是一棵树,是一种没有环路的连通图。如果再增加一条边,则一定生成环路。生成树如果有n个顶点,则一定有n-1条边。
『经典例题解析』
1.(1).如果G1是一个具有n个顶点的连通无向图,那么G1最多有多少条边?G1最少有多少条边?
(2).如果G2是一个具有n个顶点的强连通有向图,那么G2最多有多少条边?G2最少有多少条边?
(3).如果G3是一个具有n个顶点的弱连通有向图,那么G3最多有多少条边?G3最少有多少条边?
【解析】(1) G1最多n(n-1)/2条边,最少n-1条边。
(2) G2最多n(n-1)条边,最少n条边。
(3) G3最多n(n-1)条边,最少n-1条边。
(二) 图的存储及基本操作
1.邻接矩阵法
邻接矩阵法是图的一种顺序存储结构。设G有n个顶点,则可用n*n矩阵A(称为G的邻接矩阵,行标从1..n,列标从1..n)保存该有向图。
对无向图:如果vi,vj之间有边,则A的元素aij=aji=1,否则aij=aji=0;A为对称矩阵。
对有向图:如果vi有指向vj的弧,则A的元素aij=1,否则aij=0。
对带权图:如果vi,vj之间有边或者弧(vi指向vj),则A的元素aij=wij,否则aij=INFINITY。
利用邻接矩阵,可以判断任意两顶点之间是否有边(弧),并可方便求各顶点的度,图的边数等。例如:
对无向图:顶点vi的度TD(vi)是A中第i行(或者第i列)的元素之和。
对有向图:顶点vi的出度OD(vi)是第i行的的元素之和,入度ID(vi)第i列的元素之和。
对带权图:顶点vi的度的求法同上类似,但不再是求和,而是求行、列中不为零的元素个数。
例如:用邻接矩阵表示下列图:
2.邻接表法
是图的链式存储方法,类似于树的孩子表示法。针对图中的每个顶点(设v)都建立一个单链表,单链表中的结点表示依附于当前顶点v的所有的边(对有向图来说则是以v为弧尾的弧)。每个结点(称为表结点)有三个域构成:邻接点域(adjvex)表示与顶点v邻接的点在图中的位置,链域指示下一条边或弧的结点,数据域存储和边或弧相联系的其它信息(如权值等),在研究时可暂不考虑。如下:
每个链表上附设一个表头结点(也称为头结点),每个头结点包含两个域:数据域data,用于存储同顶点v相关的数据信息;链域firstarc,用于指向链表中的第一个结点。该头结点同单链表中的头结点的意义完全相同。如下:
说明:
①对无向图G来说,如果G有n个顶点,e条边,则它的邻接表表示需要n个头结点和2e个表结点。
②对无向图来说,顶点vi的度,是第i个单链表中表结点的个数。
③对有向图来说,第i个单链表中表结点的个数仅仅是vi的出度OD(vi),而要求vi的入度,必须遍历整个邻接表。这是比较复杂的。因此针对有向图,为了方便求顶点的入度,可以建立其逆邻接表,及以vi为头结点建立一个以vi为弧头的表结点的单链表。
④在邻接表上容易找到当前顶点的所有邻接顶点,但要判断两个顶点vi,vj是否相邻,则要搜索第i个和第j个单链表,比邻接矩阵要复杂(邻接矩阵只要看aij是否为1就可以了)。
图的存储方法还有十字链表法和邻接多重表法。
『经典例题解析』
1.下面结构中最适于表示稀疏无向图的是( ① ),适于表示稀疏有向图的是( ②)。
A.邻接矩阵 B.逆邻接表 C.邻接多重表 D.十字链表 E.邻接表
【答案】①C ②BDE。
2.下列哪一种图的邻接矩阵是对称矩阵?( )
A.有向图 B.无向图 C.AOV网 D.AOE网
【答案】B。
3.从邻接阵矩可以看出,该图共有(①)个顶点;如果是有向图该图共有(②) 条弧;如果是无向图,则共有(③)条边。
①.A.9 B.3 C.6 D.1 E.以上答案均不正确
②.A.5 B.4 C.3 D.2 E.以上答案均不正确
③.A.5 B.4 C.3 D.2 E.以上答案均不正确
【答案】①B ②B ③D。
4.用相邻矩阵A表示图,判定任意两个顶点Vi和Vj之间是否有长度为m 的路径相连,则只要检查( )的第i行第j列的元素是否为零即可。
A.mA B.A C.Am D.Am-1
【答案】C。
(三) 图的遍历
常见的图遍历方式有两种:深度优先遍历和广度优先遍历,这两种遍历方式对有向图和无向图均适用。
1.深度优先搜索
深度优先遍历的思想类似于树的先序遍历。其遍历过程可以描述为:从图中某个顶点v出发,访问该顶点,然后依次从v的未被访问的邻接点出发继续深度优先遍历图中的其余顶点,直至图中所有与v有路径相通的顶点都被访问完为止。
为了便于在算法中区分顶点是否已被访问过,需要创建一个一维数组visited[0..n-1](n是图中顶点的数目),用来设置访问标志,其初始值visited[i](0≤i≤n-1)为“0”,表示邻接表中下标值为i的顶点没有被访问过,一旦该顶点被访问,将visited[i]置成“1”。
int visited[0..n-1]={0,0,...0};
void DFS(AdjList adj,int v)
{//v是遍历起始点的在邻接表中的下标值,其下标从0开始
visited[v]=1; visit(adj[v].item);
for (w=adj[v].firstedge;w;w=w->next)
if (!visited[w->adjvex]) DFS(adj,w->adjvex);
}
对于无向图,这个算法可以遍历到v顶点所在的连通分量中的所有顶点,而与v顶点不在一个连通分量中的所有顶点遍历不到;而对于有向图可以遍历到起始顶点v能够到达的所有顶点。若希望遍历到图中的所有顶点,就需要在上述深度优先遍历算法的基础上,增加对每个顶点访问状态的检测:
int visited[0..n-1]={0,0,...0};
void DFSTraverse(AdjList adj)
{ for (v=0;v<n;v++) if (!visited[v]) DFS(adj,v);}
设置某个顶点v为起始点,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径的结点都被访问过;若此时尚有顶点未被访问过,则另选图中一个未曾被访问的顶点作为起始点,重复上述过程,直至图中所有顶点都被访问到为止。深度优先搜索算法可利用堆栈实现。
如右图4-11,假设从V1开始搜索,则可能的搜索序列为:
l V1 V2 V4 V8 V5 V3 V6 V7
l V1 V2 V5 V8 V4 V3 V6 V7
l V1 V3 V6 V7 V2 V4 V8 V5
l V1 V3 V7 V6 V2 V5 V8 V4
但下面的搜索序列是不可能的:
l V1 V2 V4 V5 V8 V3 v6 V7
l V1 V3 V2 V4 V5 V8 V6 V7
2.广度优先搜索
对图的广度优先遍历方法描述为:从图中某个顶点v出发,在访问该顶点v之后,依次访问v的所有未被访问过的邻接点,然后再访问每个邻接点的邻接点,且访问顺序应保持先被访问的顶点其邻接点也优先被访问,直到图中的所有顶点都被访问为止。下面是对一个无向图进行广度优先遍历的过程。
① 在广度优先遍历中,要求先被访问的顶点其邻接点也被优先访问,因此,必须对每个顶点的访问顺序进行记录,以便后面按此顺序访问各顶点的邻接点。应利用一个队列结构记录顶点访问顺序,就可以利用队列结构的操作特点,将访问的每个顶点入队,然后,再依次出队,并访问它们的邻接点;
② 在广度优先遍历过程中同深度优先遍历一样,为了避免重复访问某个顶点,也需要创建一个一维数组visited[0..n-1](n是图中顶点的数目),用来记录每个顶点是否已经被访问过。
int visited[0..n-1]={0,0,...0};
void BFS(AdjList adj,int v)
{//v是遍历起始点在邻接表中的下标,邻接表中下标从0开始
InitQueue(Q); //Q是队列
visited[v]=1; visit(adj[v].item); EnQueue(Q,v);
while (!QueueEmpty(Q)) {
DeQueue(Q,v);
for (w=adj[v].firstedge;w;w=w->next)
if (!visited[w->adjvex]) {
visited[w->adjvex]=1;
visit(adj[w->adjvex].item);
EnQueue(Q,w->adjvex); }
}
}
广度优先搜索算法可以队列实现。如上图,假设从V1开始搜索,则可能的搜索序列为:
V1 V2 V3 V4 V5 V6 V7 V8
『经典例题解析』
1. 下列说法不正确的是( )。
A.图的遍历是从给定的源点出发每一个顶点仅被访问一次
B.遍历的基本算法有两种:深度遍历和广度遍历
C.图的深度遍历不适用于有向图 D.图的深度遍历是一个递归过程
【答案】C。
2.无向图G=(V,E),其中:V={a,b,c,d,e,f},E={(a,b),(a,e),(a,c),(b,e),(c,f),(f,d),(e,d)},
对该图进行深度优先遍历,得到的顶点序列正确的是( )。
A.a,b,e,c,d,f B.a,c,f,e,b,d C.a,e,b,c,f,d D.a,e,d,f,c,b
【答案】D。
3. 设图如右所示,在下面的5个序列中,符合深度优先遍历的序列有多少?( )
a e b d f c a c f d e b a e d f c b a e f d c b a e f d b c
A.5个 B.4个 C.3个 D.2个
【答案】D。
第3题图 第4题图
4.图中给出由7个顶点组成的无向图。从顶点1出发,对它进行深度优先遍历得到的序列是( ① ),而进行广度优先遍历得到的顶点序列是( ② )。
①.A.1354267 B.1347652 C.1534276 D.1247653 E.以上答案均不正确
②.A.1534267 B.1726453 C.l354276 D.1247653 E.以上答案均不正确
【答案】①C ②C。
5.首先将如下图所示的无向图给出其存储结构的邻接链表表示,然后写出对其分别进行深度,广度优先遍历的结果。
【答案】深度优先遍历序列:125967384 ; 广度优先遍历序列:123456789
注:(1)邻接表不唯一,这里顶点的邻接点按升序排列;
(2)在邻接表确定后,深度优先和宽度优先遍历序列唯一;
(3)这里的遍历,均从顶点1开始。
6.下面的邻接表表示一个给定的无向图
(1)给出从顶点v1开始,对图G用深度优先搜索法进行遍历时的顶点序列;
(2)给出从顶点v1开始,对图G用广度优先搜索法进行遍历时的顶点序列。
第6题图
【答案】(1)V1V2V4V3V5V6 (2)V1V2V3V4V5V6
7.给出图G:
(1).画出G的邻接表表示图;
(2).根据你画出的邻接表,以顶点①为根,画出G的深度优先生成树和广度优先生成树。
第7题图
【答案】
(1)
(3)广度优先生成树
(2)深度优先生成树如上所示。
(四) 图的基本应用及其复杂度分析
1.最小(代价)生成树
最小生成树是为了解决n个城市之间构造n-1条通路,并且使得这n-1条通路的代价最小的问题。
n个城市之间最多有n(n-1)/2条通路,如何从这些可能的线路中选出n-1条,就是最小代价生成树算法。需要掌握两种算法:Prim算法和Kruskal算法。
1) Prim算法
设N= {V,{E}}是连通网,TE是最小生成树中边的集合,初始为空。
定义一个仅含一个顶点的集合 U={u0},u0∈V(u0可从顶点集合V中任意选取),则将N中的所有顶点分成了两个集合:U,V-U。
重复执行以下操作:在所有的u∈U,v∈V决定的边(u,v)∈{E}中寻找一条代价最小的边(u0,v0),将该边并入TE集合,同时vo并入U,直到U=V为止。
以上操作,通俗地讲,实际上是从两大阵营(两个图的顶点的集合)中寻找一条最短的路。
本算法中,最小代价生成树的顶点和边都是逐步生成的,开始的时候顶点集合中有一个顶点,边的集合为空。举例如下图4-12。
2) Kruskal算法
设连通图N={V,{E}},令最小生成树的初始状态为只有n个顶点而没有边的非连通图T={V,{}},T中每个顶点自成一个连通分量。
重复以下操作:在N的所有边中选择代价最小的边ei,若边ei依附在T的不同的连通分量上(即ei不仅可以将某两个原来不相连的顶点变成相连,同时还不能产生环路),则将此边加入到T的边集合中,否则舍去此边而选择下一条代价最小的边。直到T中所有顶点都在同一个连通分量为止。
通俗地说,本算法是先将所有的顶点进入一个非连通图,然后再将边按照代价最小原则依次进入,遇环取消该边,直到所有顶点连通。举例如下图4-13。
『经典例题解析』
1.已知一个无向图如下图所示,要求用Kruskal算法生成最小树(假设以①为起点,试画出构造过程)。
【答案】构造最小生成树过程如下:(下图也可选(2,4)代替(3,4),(5,6)代替(1,5))
2.最短路径
对带权图来说,从vi到vj可能不止一条路径,我们把带权路径长度最短的那条路径称为最短路径。(主要考虑有向带权图)如下图4-14:
Vi到Vj有多条路径,其中:
<Vi,Vj> :长度为8
<Vi,Vk,Vj>:长度为8
<Vi,Vk,Vm,Vj>:长度为6
则<Vi,Vk,Vm,Vj>为当前Vi到Vj的最短路径。
求图的最短路径包括两方面的问题:
l 图中某一顶点到其它各顶点的最短路径(Dijkstra算法);
l 图中每对顶点之间的最短路径(Floyd算法)。
1)从某个源点到其余各顶点的最短路径(迪杰斯特拉算法)
基本步骤为:
① 求当前v到各顶点的最短路径:设v为当前结点,先求出v和所有其它顶点vj的当前认为的最短路径,计算方法为:
若<v,vj>存在,则v和vj之间的最短路径取该弧的权值,否则取值为无穷大;
② 找中间点:找出以上求得的各最短路径的最小值,设弧<v,vk>的当前最短路径为最小值,则将vk作为下一轮求最短路径的中间点,具体做法为:将vk试着加入到v到其余顶点的当前最短路径中间,如果出现以下情况,则修改v到vj的当前最短路径和最短路径长度,否则不预修改;
③ 重复步骤②,n-1次结束(n为顶点个数)。
具体算法为:
① 引进一个辅助向量D,其每个分量D[i]表示当前所能找到的从起始点v到每个顶点vi的最短路径长度,赋值如下:
若<v,vi>存在,则 D[i]=权值,否则,D[i]=无穷大
从D中寻找一条最小的一条最短路径,如下:
D[j] = MIN {D[i]|vi∈V}
说明:如果经过以上运算后发现D[j]是无穷大,则说明v和任何其它顶点都不连通,则停止;否则,则一定可以确定一个新的中间顶点vj)
② 基于①中找到的当前最小的最短路径,下面将继续寻找下一条次最短的最短路径。设该次最短路径的终点是vk,则有如下两种可能的情况:
v――>vk(v和vk之间有弧) 或者 v -->vjàvk(v和vk不直接可达,但通过vj可达)
则其长度或者是v到vk的弧的权值,或者是D[j]加上vj到vk的弧的权值之和。一般情况下,下一条次短的最短路径的长度必定是:
D[j] = MIN{D[i]|Vi∈V-S}
其中D[i]或者是弧<v,vi>的权值,或者是D[k](Vk∈S)和弧<vk,vj>上的权值之和。
说明:S是引入的一个顶点集合,初始为{v}。每确定一条最短路径,则将终点加入其中。
举例:求下图中顶点V0到各顶点的最短路径及最短路径长度。
【答案】
① 根据图结构画出G的带权邻接矩阵,如上图A;
② 列表,得
终点 | 求解过程 | ||||
i=1 | i=2 | i=3 | i=4 | i=5 | |
V1 | ∞ | ∞ | ∞ | ∞ | ∞ 无 |
V2 | 10 (V0,V2) |
|
|
|
|
V3 | ∞
| 60 (V0,V2,V3) | 50 (V0,V4,V3) |
|
|
V4 | 30 (V0,V4) | 30 (V0,V4) |
|
|
|
V5 | 100 (V0,V5) | 100 (V0,V5) | 90 (V0,V4,V5) | 60 (V0,V4,V3,v5) |
|
Vj(路径上新增顶点) | V2 | V4 | V3 | V5 |
|
S(已求得最短路径的顶点集合) | {V0,V2} | {V0,V2,V4} | {V0,V2,V3,V4} | {V0,V2,V3,V4,V5} |
|
③ 结论:
V0到V1没有通路;
V0到V2最短路径为(V0,V2),长度为10;
V0到V3最短路径为(V0,V4,V3),长度为50;
V0到V4最短路径为(V0,V4),长度为30;
V0到V5最短路径为(V0,V4,V3,V5),长度为60。
2)每一对顶点之间的最短路径(弗洛伊德算法)
基本思想是:
从vi到vj的最短路径是以下各种可能路径中的长度最小者:
从 vi 到 vj 的所有可能存在的路径中,选出一条长度最短的路径
若<vi,vj>存在,则存在路径{vi,vj}
// 路径中不含其它顶点
若<vi,v1>,<v1,vj>存在,则存在路径{vi,v1,vj}
// 路径中所含顶点序号不大于1
若{vi,…,v2}, {v2,…,vj}存在,
则存在一条路径{vi, …, v2, …vj}
// 路径中所含顶点序号不大于2
…
依次类推,则 vi 至 vj 的最短路径应是上述这些路径中,路径长度最小者。
3.拓扑排序
1)有向无环图的定义:一个无环的有向图称做有向无环图(directed acycline praph)。简称DAG图。有向无环图是描述含有公共子式的表达式的有效工具 。
2)拓扑排序:是有向图的一个重要操作。在给定的有向图G中,若顶点序列vi1,vi2,...,vin满足下列条件:若在有向图G中从顶点vi到顶点vj有一条路径,则在序列中顶点vi必在顶点vj之前,便称这个序列为一个拓扑序列。求一个有向图拓扑序列的过程称为拓扑排序。
举例:计算机专业的学生应该学习的部分课程及其每门课程所需要的先修课程。如图4-15所示。
课程代号 | 课程名称 | 先修课程 |
c1 | 高等数学 |
|
c2 | 计算机基础 |
|
c3 | 离散数学 | c1,c2 |
c4 | 数据结构 | c2,c3 |
c5 | 程序设计 | c2 |
c6 | 编译原理 | c4,c5 |
c7 | 操作系统 | c4,c9 |
c8 | 普通物理 | c1 |
c9 | 计算机原理 | c8 |
拓扑排序的方法:
1> 从图中选择一个入度为0的顶点且输出之;
2> 从图中删掉该顶点及其所有以该顶点为弧尾的弧。
反复执行这两个步骤,直到所有的顶点都被输出,输出的序列就是这个无环有向图的拓扑序列。细心的读者可能会发现:在每一时刻,可能同时存在多个入度为0的顶点,选择注:表中c1~c9列表示的是每个顶点的入度。
4.关键路径
如果在带权的有向图中,用顶点表示事件,用有向边表示活动,边上的权值表示活动的开销,则此带权的有向图称为边活动网 (Activity on edge network) ,简称AOE网。如图4-16所示。
顶点表示一个事件;顶点的入边表示该事件发生所需的的活动;顶点的出边所表示当该事件发生后,后续的活动都可以开展了。
AOE网中有些活动可以并行进行,所以完成整个工程的最短时间是从开始顶点到完成顶点的最长路径长度,路径长度为路径上各边的权值之和。把开始顶点到完成顶点的最长路径称为关键路径。
1)几个相关的概念
l e (i):活动ai的最早开始时间;
l l(i):活动ai的最迟开始时间;
l ve(j):事件的最早发生时间;
l vl(j):事件的最迟发生时间;
l 源点:入度为0的顶点;
l 汇点:出度为零的顶点。
2)关键活动
关键活动就是e(i) = l(i)的活动。
l(i)-e(i)表示完成活动ai的时间余量,是在不延误工期的前提下,活动ai可以延迟的时间。
3)关键路径算法
(1) 输入e条弧<j,k>,建立AOE网的存储结构。
(2) 从源点v0出发,令ve(0)=0,求 ve(i) 1<=i<=n-1。
(3) 从汇点vn出发,令vl(n-1) = ve(n-1),
求 vl(i) 2<=i<=n-2。
(4) 根据各顶点的ve和vl值,求每条弧s的
最早开始时间e(s) 和最迟开始时间l(s)。
若某条弧e(s)=l(s),则为关键活动。
注意:求关键路径是在拓扑排序的前提下进行的,不能进行拓扑排序,自然也不能求关键路径。
4)算法分析:
设AOE网有n个顶点,e条边,在求事件可能的最早发生时间及允许的最迟发生时间,以及活动的最早开始时间和最晚开始时间时,都要对图中所有顶点及每个顶点边表中所有的边结点进行检查,时间花费为O(n+e),因此,求关键路径算法的时间复杂度为O(n+e)。
『经典例题解析』
1.对图示的AOE网络,计算各活动弧的e(ai)和l(ai)的函数值,各事件(顶点)的ve(Vj)和vl (Vj)的函数值,列出各条关键路径。
【答案】
顶点 | α | A | B | C | D | E | F | G | H | W |
Ve(i) | 0 | 1 | 6 | 3 | 4 | 24 | 13 | 39 | 22 | 52 |
Vl(i) | 0 | 29 | 24 | 3 | 7 | 31 | 13 | 39 | 22 | 52 |
活动 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | a8 | a9 | a10 | a11 | a12 | a13 | a14 | a15 | a16 | a17 |
e(i) | 0 | 0 | 0 | 0 | 1 | 6 | 6 | 3 | 3 | 4 | 24 | 13 | 13 | 13 | 39 | 22 | 22 |
l(i) | 28 | 18 | 0 | 3 | 29 | 24 | 31 | 34 | 3 | 7 | 31 | 20 | 36 | 13 | 39 | 22 | 40 |
关键路径是:
活动与顶点的对照表:a1<α,A> a2<α,B> a3<α,C> a4<α,D> a5<A,E> a6<B,E> a7<B,W> a8<C,G>
a9<C,F> a10<D,F> a11<E,G> a12<F,E> a13<F,W> a14<F,H> a15<G,W> a16<H,G> a17<H,W>。