数据结构学习笔记——图的应用2(拓扑排序、关键路径)

简介: 数据结构学习笔记——图的应用2(拓扑排序、关键路径)

一、拓扑排序


(一)DAG图和AOV网


对于一个有向图,若图中不存在回路(环),则称该图是一个DAG图;若以DAG图中的顶点表示活动,以边表示活动的先后次序,则称其是一个AOV网。

例如,这就是一个DAG图:

1667297026041.jpg



(二)拓扑排序的概念


对于一个DAG图,其所有顶点组成一个线性序列,且每个顶点只出现一次,对于图G=(V,E)中的任意一对顶点A和B,顶点A在线性序列中排在顶点B的前面,即<A,B>∈E(G),图中存在由A到B的路径,则这种序列称为拓扑序列,称为拓扑排序。

若一个有向无回路图的拓扑序列是唯一的,对其而言并不能唯一确定该图。


(三)拓扑排序的步骤


对于一个AOV网进行拓扑排序:

1、从网中找选择一个没有前驱,即入度ID(v)=0的顶点;

2、删除该顶点且删除由该顶点的所有起始边(出度的边),并将该顶点输出;

3、重复(1)和(2)步骤,直至AOV网为空或不存在没有前驱的结点为止;

4、最后得到一个序列,即拓扑排序。


AOV网的拓扑排序的序列不止一个,也可以能有多个,是由于当网中有多个入度为0的顶点时,进行拓扑排序,得到的序列不止一种。

例如,对于下面这个图,对其进行拓扑排序:

1667297039700.jpg


1、由该图可知,ID(V1)=0、ID(V6)=0,即入度为0的顶点有V1、V6。

2、以V1开始(若以V6序列开始也可以,只是得到的拓扑序列不同),删除顶点V1以及其出度并输出该顶点,得到如下:

1667297048333.jpg

3、由得到的图可知,ID(V3)=0、ID(V6)=0,即入度为0的顶点为V3和V6,以V3继续(若以V6序列也可以,只是得到的拓扑序列不同),删除顶点V3以及其出度并输出该顶点,得到如下:

1667297055414.jpg

4、由得到的图可知,ID(V6)=0,即入度为0的顶点为V6,删除顶点V6以及其出度并输出该顶点,得到如下:

1667297062018.jpg

5、由得到的图可知,ID(V5)=0,即入度为0的顶点为V5,删除顶点V5以及其出度并输出该顶点,得到如下:

1667297072210.jpg

6、由得到的图可知,ID(V4)=0,即入度为0的顶点为V4,删除顶点V4以及其出度并输出该顶点,得到如下:

1667297082660.jpg

7、加上最后的顶点V2,输出该顶点,此时图中所有顶点都输出,即得到该图的一个拓扑序列为V1、V3、V6、V5、V4、V2。


(四)拓扑排序的时间复杂度


若一个有向图的所有顶点不能排成一个拓扑序列(有向图存在回路,则一定不存在拓扑排序),由于该图中一定存在回路,即该图中存在含有顶点数目大于1的强连通分量。

对于图G=(V,E),当采用邻接矩阵存储图时拓扑排序的时间复杂度为O(|V|2);而采用邻接表存储图时,由于在输出顶点时还要删除其所有起始边(出度),所以拓扑排序的时间复杂度为O(|V|+|E|)。


二、逆拓扑排序和DFS算法的应用


(一)逆拓扑排序


若将拓扑排序的步骤中的一开始的针对入度进行排序改为针对出度,则以下是逆拓扑排序的步骤,对于一个AOV网进行逆拓扑排序:

1、从网中找选择一个没有后继,即出度OD(v)=0的顶点;

2、删除该顶点且删除由该顶点的所有终止边(入度的边),并将该顶点输出;

3、重复(1)和(2)步骤,直至AOV网为空或不存在没有后继的结点为止;

4、最后得到一个序列,即逆拓扑排序。


与拓扑排序一样,AOV网的逆拓扑排序的序列不止一个,也可以能有多个,是由于当网中有多个出度为0的顶点时,进行逆拓扑排序,得到的序列不止一种。

例如,对于下面这个图,对其进行拓扑排序:


1667297101657.jpg

1、由该图可知,OD(V3)=0、OD(V5)=0,即出度为0的顶点有V3、V5。

2、以V3开始(若以V5序列开始也可以,只是得到的逆拓扑序列不同),删除顶点V3以及其入度并输出该顶点,得到如下:

1667297109889.jpg

3、由得到的图可知,OD(V5)=0,即出度为0的顶点为V5,以V5继续,删除顶点V5以及其入度并输出该顶点,得到如下:

1667297116290.jpg

4、由得到的图可知,OD(V4)=0,即出度为0的顶点为V4,以V4继续,删除顶点V4以及其入度并输出该顶点,得到如下:

1667297125000.jpg

5、由得到的图可知,OD(V2)=0,即出度为0的顶点为V2,以V2继续,删除顶点V2以及其入度并输出该顶点,得到如下:

1667297132564.jpg

6、加上最后的顶点V1,输出该顶点,此时图中所有顶点都输出,即得到该图的一个逆拓扑序列为V3、V5、V4、V2、V1。


(二)DFS算法的应用


对于一个AOV网也可以采用DFS算法,由于当从有向无环图中某一顶点开始深度优先搜索时,最先得到的顶点即为出度等于0的顶点,所以通过DFS算法得到的顶点序列刚好为逆向的拓扑序列。(DFS算法的知识点可以看之前的文章:图的遍历算法(深度优先搜索和广度优先搜索))


三、关键路径


(一)AOE网


对于AOV网,它是以顶点来表示活动、边上无权值,只表示活动的先后次序;而对于AOE网,是以边来表示活动,且边上有权值,表示完成该活动的开销(时间),其对比如下表:

1667297167301.jpg


(二)源点和汇点


在一个表示工程的AOE网中,只存在一个入度等于0,即ID(v)=0的顶点,称为源点,代表整个工程的开始;同样,也只存在一个出度等于0,即OD(v)=0的顶点,称为汇点,代表整个工程的结束。


(三)关键路径的概念


在AOE网中,由源点到汇点的有向路径可能有多条,从而完成活动的路径长度也不同,将所有路径中具有最大路径长度的路径称为关键路径,且这条路径上的所有活动称为关键活动,是决定整个工程的关键因素,关键路径是整个工程所完成的最短时间。

一个AOE网的关键路径不唯一,若延长关键路径上的关键活动的时间,则整个工程的时间也会随着延长;另外,只有缩短所有的关键路径上共有的任意一个关键活动的持续时间,才会缩短关键路径长度。


(四)关键路径的算法步骤


1、根据所给的图,求得该图的拓扑有序序列和逆拓扑有序序列;

2、通过得到的序列,求得每个事件的最早发生时间和最迟发生时间:

3、根据(2)得到的结果,求得每个活动的最早发生时间和最迟发生时间。

4、根据(3)得到的结果,找到最早发生时间和最迟发生时间相同的活动,就得到了关键活动,由关键活动连成的路径即为关键路径。


例如,下面是一个AOE网,求其关键路径。


1667297189250.jpg

1、首先求其拓扑序列和逆拓扑序列:

拓扑序列和逆拓扑序列分别为V3、V1、V4、V2、V6、V5和V5、V6、V2、V4、V1、V3。

2、设ve(V)为顶点V代表的事件的最早发生时间,依照拓扑序列的先后顺序求各顶点所对应事件的最早发生时间:

设事件0的开始时间为0,即ve(V3)=0,可得:

ve(V1)=ve(V3)+3=0+3=3;

ve(V4)=ve(V3)+3=0+3=3;

由于到V2的路径有两条,分别是V1→V2和V4→V2,要取其中权值最大者,也就是最早发生时间,ve(V2)=max{ve(V1)+2,ve(V4)+6}=max{3+2,3+6}=9;

ve(V6)=ve(V4)+1=3+1=4;

由于到V5的路径有两条,分别是V2→V5和V6→V5,要取其中权值最大者,也就是最早发生时间,ve(V5)=max{ve(V2)+5,ve(V6)+2}=max{9+5,4+2}=14。

3、设vl(V)为顶点V代表的事件的最迟发生时间,最迟发生时间是在不推迟整个工程完成的前提下,该事件最迟必须发生的时间,依照逆拓扑序列的先后顺序求各顶点所对应事件的最迟发生时间,且由事件的最早发生时间,可得:

vl(V5)=ve(V5)=14;

vl(V6)=vl(V5)-2=14-2=12;

vl(V2)=vl(V5)-5=14-5=9;

由于回V4的路径有两条,分别是V3→V1和V3→V4,要取其中权值最小者,也就是最迟发生时间,vl(V4)=min{vl(V2)-6,vl(V6)-1}=min{9-6,12-1}=3;

vl(V1)=vl(V2)-2=9-2=7;

由于回V3的路径有两条,分别是V4→V2和V4→V6,要取其中权值最小者,也就是最迟发生时间,vl(V3)=min{vl(V1)-3,vl(V4)-3}=min{7-3,3-3}=0。

4、求每个活动的最早发生时间和最迟发生时间,设e(ak)和l(ak)为当前活动ak的最早发生时间和最迟发生时间,由于事件代表一个新活动的开始或旧活动的结束,所以事件的最早发生时间就是由这个事件所发出的活动的最早发生时间,即ve(V);而活动的最迟发生时间是事件的最迟发生时间减去以它为结束点的活动的持续时间:

1667297199157.jpg

(1)活动的最早发生时间:

e(a0)=e(a1)=ve(V3)=0;

e(a2)=e(a3)=ve(V4)=3;

e(a4)=ve(V1)=3;

e(a5)=ve(V2)=9;

e(a6)=ve(V6)=4。

(2)活动的最迟发生时间:

l(a0)=vl(V1)-3=7-3=4;

l(a1)=vl(v4)-3=3-3=0;

l(a2)=vl(V2)-6=9-6=3;

l(a3)=vl(V6)-1=12-1=11;

l(a4)=vl(V2)-2=9-2=7;

l(a5)=vl(V5)-5=14-5=9;

l(a6)=vl(V5)-2=14-2=12;

由以上得到的活动的最早/最迟发生时间,整理得关键活动,即最早最早发生时间和最迟发生时间相同的活动,如下:

活动 最早发生时间 最迟发生时间 关键活动
a0 0 4
a1 0 0
a2 3 3
a3 3 11
a4 3 7
a5 9 9
a6 4 12



5、由关键活动所连成的路径即为所求的关键路径,可知关键路径为:

V3→V4→V2→V5,

关键路径所持续的时间为整个工程所持续的时间(整个图中所有活动完成的时间):

a1+a2+a5=0+3+9=12。


例、下图所示的AOE网表示一项包含8个活动的工程,求活动d的最早开始时间和最迟开始时间。

1667297218122.jpg


解:活动d的最早开始时间为事件2的最早发生时间,即max{a,b+c}=max{3,12}=12;求出图的关键路径长度,先求得拓扑排序为1、3、2、4、5、6:

设ve(1)=0,

ve(2)=ve(1)+3=3;

ve(3)=ve(1)+8=8;

ve(4)=ve(2)+7=10;

ve(5)=max{ve(2)+6,ve(3)+10}=max{9,18}=18;

ve(6)=max{ve(4)+6,ve(5)+9}=max{16,27}=27;

所以关键路径长度为27。

由于活动d的最迟开始时间为该活动弧的终点所表示的事件的最迟发生时间与该活动所需时间之差,即min{27-g}=min{21},

所以活动的最迟开始时间为21-d=21-7=14。


相关文章
|
5天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
|
18天前
|
存储 Java
【数据结构】优先级队列(堆)从实现到应用详解
本文介绍了优先级队列的概念及其底层数据结构——堆。优先级队列根据元素的优先级而非插入顺序进行出队操作。JDK1.8中的`PriorityQueue`使用堆实现,堆分为大根堆和小根堆。大根堆中每个节点的值都不小于其子节点的值,小根堆则相反。文章详细讲解了如何通过数组模拟实现堆,并提供了创建、插入、删除以及获取堆顶元素的具体步骤。此外,还介绍了堆排序及解决Top K问题的应用,并展示了Java中`PriorityQueue`的基本用法和注意事项。
23 5
【数据结构】优先级队列(堆)从实现到应用详解
|
29天前
|
存储 机器学习/深度学习
【数据结构】二叉树全攻略,从实现到应用详解
本文介绍了树形结构及其重要类型——二叉树。树由若干节点组成,具有层次关系。二叉树每个节点最多有两个子树,分为左子树和右子树。文中详细描述了二叉树的不同类型,如完全二叉树、满二叉树、平衡二叉树及搜索二叉树,并阐述了二叉树的基本性质与存储方式。此外,还介绍了二叉树的实现方法,包括节点定义、遍历方式(前序、中序、后序、层序遍历),并提供了多个示例代码,帮助理解二叉树的基本操作。
44 13
【数据结构】二叉树全攻略,从实现到应用详解
|
1月前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
44 10
【数据结构】链表从实现到应用,保姆级攻略
05_用一个栈实现另一个栈的排序
05_用一个栈实现另一个栈的排序
|
6天前
|
存储 数据安全/隐私保护 Python
Python常用数据结构——字典的应用
Python常用数据结构——字典的应用
11 2
|
8天前
|
JSON 前端开发 JavaScript
一文了解树在前端中的应用,掌握数据结构中树的生命线
该文章详细介绍了树这一数据结构在前端开发中的应用,包括树的基本概念、遍历方法(如深度优先遍历、广度优先遍历)以及二叉树的先序、中序、后序遍历,并通过实例代码展示了如何在JavaScript中实现这些遍历算法。此外,文章还探讨了树结构在处理JSON数据时的应用场景。
一文了解树在前端中的应用,掌握数据结构中树的生命线
|
8天前
|
存储 JSON NoSQL
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
这篇文章是关于Redis基本数据结构的学习笔记,包括了String、Hash、Set、List和SortedSet的介绍和常用命令。文章解释了每种数据结构的特点和使用场景,并通过命令示例演示了如何在Redis中操作这些数据结构。此外,还提供了一些练习示例,帮助读者更好地理解和应用这些数据结构。
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
|
26天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
27天前
|
Java
【数据结构】栈和队列的深度探索,从实现到应用详解
本文介绍了栈和队列这两种数据结构。栈是一种后进先出(LIFO)的数据结构,元素只能从栈顶进行插入和删除。栈的基本操作包括压栈、出栈、获取栈顶元素、判断是否为空及获取栈的大小。栈可以通过数组或链表实现,并可用于将递归转化为循环。队列则是一种先进先出(FIFO)的数据结构,元素只能从队尾插入,从队首移除。队列的基本操作包括入队、出队、获取队首元素、判断是否为空及获取队列大小。队列可通过双向链表或数组实现。此外,双端队列(Deque)支持两端插入和删除元素,提供了更丰富的操作。
30 0
【数据结构】栈和队列的深度探索,从实现到应用详解