算法与数据结构(七) AOV网的拓扑排序(Swift版)

今天博客的内容依然与图有关,今天博客的主题是关于拓扑排序的。拓扑排序是基于AOV网的,关于AOV网的概念,我想引用下方这句话来介绍:html

AOV网:在现代化管理中,人们经常使用有向图来描述和分析一项工程的计划和实施过程,一个工程常被分为多个小的子工程,这些子工程被称为活动(Activity),在有向图中若以顶点表示活动,有向边表示活动之间的前后关系,这样的图简称为AOV网。git

说的简单点,AOV网就是表示一个工程中某些子项的前后顺序。就拿工地搬砖来讲吧,只有砖厂送来砖,工人才能搬。那么砖厂送砖就是搬砖的前提。先这么一聊,下方会给出详细的介绍。废话少说进入今天的主题。github

 

1、AOV网与拓扑排序数组

本篇博客咱们先聊一下AOV网和拓扑排序的关系,下方是咱们列举的一个很是简单的例子,固然下方的这个图就是一个简单的AOV图,麻雀虽小,五脏俱全。在下方的AOV图中,送砖和找人是并列的,先执行谁都行。不过搬砖的前提是即送完了砖也找完了人,而后就能够开始搬砖了,因此送砖和找人就是搬砖的前提。那么让搬砖这件事情顺利进行下去的顺序有"送砖->找人->搬砖"或者“找人->送砖->搬砖”这两个序列,而这两个序列都是拓扑序列数据结构

  

生成“送砖->找人->搬砖”这个序列的过程咱们称之为拓扑排序。若是非得说的官方和抽象点,那么仍是引用拓扑排序的定义吧,下方就是拓扑排序的定义:post

拓扑排序对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中全部顶点排成一个线性序列,使得图中任意一对顶点uv,若边(u,v)E(G),则u在线性序列中出如今v以前。一般,这样的线性序列称为知足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序获得该集合上的一个全序,这个操做称之为拓扑排序。url

 上面这个定义就比较抽象了,固然仍是咱们搬砖的例子好理解一些。在有向无环图中的结点若是有入度的话,那么就说明该结点优先级要低于那些能够到达改点的结点。而那些没有入度的结点的优先级就比较高,这些结点的完成不依赖与其余结点。这样说若是有些抽象的话,那么咱们就看下方拓扑排序详细的示例图。spa

 

2、拓扑排序示意图3d

本部分咱们将会给出拓扑排序详细的示意图。拓扑排序实现是依赖于栈与队列的数据结构,栈用来暂存那些入度为0的结点,而队列负责存储已经生成的拓扑序列。由于前几篇关于图的博客,咱们都使用了相同的图结构。本篇博客也不例外,咱们依然会使用以前的有向图,由于以前的图是有向无环图,因此是能够生成拓扑序列的。下方就是要生成拓扑序列的有向无环图。htm

在下方的有向无环图每一个结点上有一个绿色的数字,该数字记录的就是该结点的入度。入度为零,那么该数字就是0,若是入度为1,那么该数字就是1。

  

下方是下图拓扑排序每一步的示意图。接下来咱们将会给出下方每一步示意图的详细解说。

  • (1):首先将图中入度为0的结点入栈,由于此图中只有A结点的入度为0,因此咱们将A入栈。
  • (2):将A从栈中Pop到咱们的拓扑队列中,将那些以A发出的边为入度的结点的度数减一。对于此示例来讲也就是将B和F入度减1. 由于B和F的入度数减一后成了0, 因此也将B, F这两结点入栈
  • (3):将F从栈中Pop到拓扑队列中,由于图中有 F->G和F->E两条边,因此 将G和E的入度数减一。由于E的入度数减一后为0,因此将E入栈。
  • (4):将E从栈中Pop到拓扑队列中,由于图中E->H和E->D两条边, 因此讲H和D的两个结点的入度减一。两个结点减一操做后没有入度为零的节点,因此本步没有结点入栈。
  • (5):接着从栈中Pop结点到拓扑队列中,将B结点Pop出栈入拓扑队列。由于C、I、G结点与B相连,B添加进拓扑序列后,这三个结点的入度都减一。C和G的入度减一后为0,因此将其加入栈中。
  • (6):将C从栈中pop到拓扑队列中,与C相连的结点是I和D, 将这两个结点的入度减1。I的入度减一后为0,将其Push到栈中暂存。
  • (7):将I结点从栈中pop到拓扑队列中, D与I相连,将D的入度再次减一。本轮没有要入栈的结点。
  • (8):将G从栈中pop到拓扑队列中, G与H和D相连,将D与H的入度减一。H的入度减一操做后入度为零将其入栈。
  • (9):将H从站中pop到拓扑队列中, D与H相连,将D的入度减一,减一后为0,因此将D入栈。
  • (10):将D从栈中pop到拓扑队列中,此刻栈中为空,拓扑序列生成完毕。

  

  

上面这些步骤已经很详细了,上面这些步骤搞明白后,给出代码实现就简单多了。下方咱们会给出具体的代码实现。

 

3、拓扑排序的代码实现

讲完概念和原理后,接下来咱们就要开始实践了。本部分就会给出具体的代码实现,固然咱们依然采用Swift语言来作。首先咱们建立要依赖的队列和栈,而后再构建有向图的邻接链表,最后给出拓扑排序的代码实现。进入本部分的主题:

 

1.队列与栈

接下来咱们就要实现拓扑序列生成时要使用的栈与队列,关于栈与队列本篇博客就不作过多的赘述了,由于咱们以前已经对栈与队列作了详细的介绍。关于栈与队列更详细的内容请查看以前的博客《栈与队列的线性和链式表示(Swift面向对象版)》。

下方这段代码段就是咱们本篇博客要使用的栈的类,固然是简化版的,也就是对Array作了一个简单的封装。栈中存储的数据类型是咱们邻接链表的结点。具体代码以下所示。

  

 

下方则是咱们存储拓扑序列的队列,固然也是基于Array的简单封装。 

  

 

2.有向图的构建

接下来咱们来建立咱们的有向图。本篇博客所使用的有向图咱们是使用邻接链表来表示的。下方这段代码段就是邻接链表的结点,固然在以前不知一篇博客中咱们使用到了下方这个结点。本篇博客中的weightNumber不单单只存边的权值,在数组中的结点的weightNumber咱们用来存储该结点的入度

  

 

下方这段代码就是有向图的建立,在网邻接链表上挂入结点时,要讲被挂入的结点的入度加1便可。由于下方代码与以前图的建立的代码相似,在此就不作过多赘述了。

  

 

下方这两个截图则是上述代码段的输入和输出。根据输出的结果咱们不难看出咱们所建立的图就是一个有向图。

  

  

 

三、拓扑序列的生成

接下来就是咱们本篇博客代码实现的核心了。咱们将基于上面建立的AOV网来生成拓扑序列。其实下方生成拓扑序列的代码就是上述示例描述的具体实现。接下来咱们将具体的说下下方这段拓扑排序的代码。主要归纳起来分为下方三步:

  • (1):首先初始化咱们所须要的栈,而后 遍历AOV网中全部的结点,将入度为0的结点添加到咱们的栈中暂存。
  • (2):循环将咱们栈中的元素添加到拓扑队列中。每从栈中Pop出一个结点就把与该结点相连的结点的入度减1,若是减一后该结点的度数为0则将其入栈。而后继续下一轮的循环。
  • (3):当栈中没有暂存的结点后,说明拓扑序列生成完毕。若是拓扑队列中的元素要小于图结点的个数,那么说明图中存在环路,不能生成相应的拓扑序列。

  

 

下方截图就是咱们以前建立的有向图所生成的拓扑序列,以下所示:

  

 

至此,咱们本篇博客的内容也就结束了,下方依然是咱们本篇博客所涉及Demo的分享连接,以下所示:

github分享连接:https://github.com/lizelu/DataStruct-Swift/tree/master/TopoLogicalSort

相关文章
相关标签/搜索