0
点赞
收藏
分享

微信扫一扫

Lecture 20

蛇发女妖 2022-04-03 阅读 95

绪论

我们前面介绍了图的遍历和最小生成树,这一章节介绍图中的另一个重要知识点——拓扑排序。有关拓扑排序,我们不应该仅限于了解拓扑排序的算法,还应该了解拓扑排序的背景(为了解决什么样的问题),拓扑排序有关的数学(图论)知识,算法的运行过程,算法的运行时,拓扑排序另外的应用。

第十八章应该是贪心算法,但我个人认为应该先把图上的知识点先介绍完全,所以将顺序调换了一下。


Motivation

拓扑排序起源于现实中的工程(活动)问题:假设存在若干个工程(活动)希望完成,但是某些工程需要在另外一些工程完成的基础上才能开始进行,工程之间的这种关系我们称为依赖关系(dependency)。

一个工程对另一个工程的依赖构成偏序关系,偏序关系可以看作结点到结点的有向边,于是上述的工程问题可以构成一个有向图。两个结点至多只需要存在一条边,并且对于若干个能够全部完全的工程,不应该存在对于两个工程互相依赖的情况,转化有向图中即不同时存在结点A到结点B和结点B到结点A的路径,即不存在环。

综上,能够全部完成的工程问题构成有向无环图(DAG)。

在这里插入图片描述

良好的工程问题形成的DAG如下:

在这里插入图片描述


Topological sorting

拓扑排序就是一种算法来解决上述的问题:根据给定的若干个工程以及工程之间的依赖关系,拓扑排序能够将这些给定的工程排出先后完成的顺序(每个工程在进行时,它需要依赖的工程在之前就已经完成了)。

在这里插入图片描述
很显然这样的序列不止一种。

算法的本身很简单:寻找图中0度的结点加入当前拓扑排序的序列,然后在图上离散定义上删去该点,重复进行这样的操作直到所有的点都加入到我们想要的序列中。

在这里插入图片描述


Key Lemma

我们观察到后面阶段的图为前面阶段图的导出子图,于是算法的可行性就需要子图与初始图一起保持相同的性质,具体结论如下:

1.DAG的导出子图仍为DAG。
2.对于任意的DAG,能够找到至少一个入度为0的结点。

(另有一个结论是每个DAG都存在至少一个拓扑序列,我们能够知道DAG和能够解决的工程问题中工程的关系一一对应,结论等价于每个能够解决的工程问题都存在一个工程的拓扑序列,结论是显然的)

第一个结论是显然的,第二个结论从反证法来看也是显然的,我们假设存在一个DAG中没有任何一个结点的入度为0,我们在结点中选取任意一个结点,该节点一定有前继结点,我们将前继结点放在选取的结点之前,这样重复操作可以得到一个不断向左变长的序列,序列中每一个结点都是下一个结点的前继结点。

在不断向左变长的过程中,在至多进行定点总个数-1向左变长的操作之后,最左结点的前继结点一定会出现在右边的结点序列之中(假设中要求每个结点都有前继结点),此时会产生环,和DAG本身的性质矛盾。

于是算法的可行性可以保证,也就是说根据该算法至少一定能够得到一个序列。这个序列也一定满足拓扑序列的要求:因为对于该序列中的任意一个结点,该节点在加入序列时在当时状态下的图中入度为0。它原本可能存在的前继结点,在之前已经加入到序列中,即从图中已经删去,所以该节点能够作为入度为0的结点加入序列,这样就满足了拓扑序列的要求了。


Implementation

实现算法最基础的方法就是,每次遍历所有的结点找到0度的结点,然后删去该节点,即修改与它邻接的顶点的入度值,重复到长度为|V|的拓扑序列被得到。

这么做每次寻找0度结点的复杂度为O(|V|),算法的总复杂度即为O(|V|2)。

如果仔细地观察算法过程中寻找0度结点过程,我们能够发现,很多结点的判断是重复的。因为我们每次循环删去一个结点,除了与该节点相邻,且为终点的结点,其余节点的入度并不发生改变。

也就是说我们每次删去一个结点后,再下次循环时找到的入度为0的结点一定出现在本次循环被删去结点的邻接结点之中,我们只需要修改相邻结点的入度,然后在修改的过程中寻找到下一轮的入度为0的结点。

每轮的复杂度为该节点相邻节点的个数,总复杂度即是边的数量O(|V|+|E|)。

具体实现的数据结构可以用队列,初始化将0度的结点都放入队列中(这点很重要,否则开始入度为0的结点不能在后面的过程找到),每次出队0度的结点,然后将删去该节点后入度变为0的结点入队,这也是课件上的做法。

另外有做法是用栈作为数据结构,也是初始化将0度的结点都放入栈中,每次对栈顶的结点进行操作,然后将删去该节点后入度为0的结点压入栈中。这种做法我在前面分析过,和另一种递归的做法本质是一样的(寻找一个0度的结点,向下进行DFS)。

事实上,对于DAG的任意两个连通分量上的结点,他们之间不存在任何的依赖关系,上述做法的实质就是对DAG的各个连通分量(导出子图DAG)进行DFS或者BFS。


Finding the critical path

英语忘了差不多的我在看大纲的时候还以为是找环形路hhhhh,实际上是critical是关键的意思,不过拓扑排序确实能够用来找环形路就是了。

那什么叫做critical path呢?

在前面工程问题对应的DAG上,给每个活动一个完成它的代价属性。在完成所有工程的过程中,我们可以同时并行执行若干个不互为前提的工程,我们希望用最小的代价完成并行完成所有的工程。

每项工程的critical time是这项任务能够被完成的最早时间(最小代价),所有活动全部完成的最小代价称为该工程问题,决定了这个最小的代价的DAG上的路径称为critical path。

在这里插入图片描述

前面提到可以将拓扑排序算法实现的实质是对各个连通分量的BFS和DFS,寻找critical time和critical path的问题也是建立在工程问题的基础上,且任意两个不同连通分量的结点的critical time互不影响,同理我们可以用类似的策略来计算各个结点的critical time:

给每个结点定义另一个代价属性称为additional time用来表示完成该节点对应的工程需要依赖的工程被全部完成的最小代价,即有additional time+该工程的执行时间=critical time,additional time为前继结点的critical time中最大的值,在进行拓扑排序时,每次修改入度时修改计算additional time,入度为0的结点进行操作时计算critical time即可(因为critical time和additional time是加上一个工程执行时间可以得到的关系,可以只用一个变量critical time变量来存储additional time的值)。

在这里插入图片描述


总结

这一章介绍了拓扑排序的背景和应用以及多种实现方式,个人觉得不应该将拓扑排序和BFS和DFS割裂开,因为基础的拓扑排序从实质上就是对各连通分量进行DFS和BFS,在应用时也要想到BFS和DFS的功能。

举报

相关推荐

FNLP lecture 7

Lecture 5

Lecture 4 Text Classification

Lecture 17-1

Lecture1 code

Lecture 18 Information Extraction

0 条评论