前言大家好,我是bigsai。拓扑排序,很多人可能听说过但不了解的一种算法。大多数不知道的人会问这样一个问题:这是某种排序算法吗?这好像是图论算法?将线性序列排列到图的顶点。而且不一定是独一无二的。什么是拓扑排序?对有向无环图(DirectedAcyclicGraph,简称DAG)G进行拓扑排序,就是将G中的所有顶点排列成一个线性序列,使得图中任意一对顶点u和v,若边∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑序(TopologicalOrder)的序列,简称拓扑序列。简单地说,就是从某个集合上的偏序得到一个集合上的全序,这种操作称为拓扑排序。拓扑排序的作用是什么?拓扑排序有很多应用。当某些项目有多个进程时,拓扑排序可以获得有效的处理顺序,而游戏中的某些任务必须满足匹配的拓扑排序才能解锁。下一层,还有一些项目或者环境的依赖集……当然,上面的例子可能不够具体。离我们更近一点的是课程学习。比如,在学习数据结构之前,你基本上需要学习C或C++,因为数据结构需要理解并能够使用C++代码;在学习操作系统和计算机网络之前,你必须学习数据结构课程,因为它涉及到很多数据结构和算法;在学习JavaWeb开发之前,你必须学习JavaSE和HTML。两门课程;不同学院的课程差别很大,但是可以很好的衔接起来,因为课程安排满足拓扑排序。还是看不懂拓扑排序?让我举一个更详细的例子。Java系列教程部分可能有以下顺序:比如学习Java系列(部分)从Java基础,到JSP/Servlet,到SSM,再到SpringBoot,SpringCloud等,是一个循序渐进、有前提的过程.学习JSP,首先要掌握Java和HTML的基础知识。学习框架,必须掌握JSP/Servlet和JDBC。那么,这个学习过程就构成了一个拓扑序列。当然这个顺序不是唯一的,不相关的科目你可以选择顺序(比如Html和Java可以随便开始)。上述序列可以简单表示为:这五个是可选的学习方案,可以作为课程安排的参考,这五个是上述有向无环图(DAG)的拓扑序列,只是小的选择策略不同(先学Java还是HTML都无所谓,但必须满足整个顺序要求),不影响规则的顺序!对于拓扑排序,还有一些比较专业的术语需要记住:DAG:DirectedAcyclicGraphAOVNetwork:数据在顶点,顶点代表活动,边代表活动序列,可以理解作为一种面向对象。AOE网络:数据在边上,顶点代表事件,有向边代表活动,边上的权重代表活动持续时间,可以理解为面向过程。很多人不知道AOE网络是干什么用的。拓扑排序是为了解决一个项目能否按顺序进行的问题,但有时也需要解决项目完成所需的最短时间。而AOE常用于寻找关键路径(内容和算法这里不再详细介绍),图片来自https://www.cnblogs.com/svod5306/p/14723338.html)。我们今天说的拓扑排序就是在AOV中找到一个不破坏图结构的序列。对于有向无环图,需要注意图:如果A在B前面,则不存在B在A前面的路径(无法形成环路)。在拓扑序列中,相邻的两个节点只需要满足上下文关系即可,不一定需要相邻(节点只需要满足相对上下文关系即可,因此拓扑排序不一定唯一)。算法分析上面简单介绍了拓扑排序,下面详细介绍拓扑排序的方法。正常的步骤是(方法不一定唯一):1.从DAG图中找到一个没有前驱的顶点输出。可以遍历入度为0的节点,也可以使用优先级队列来维护。2.删除从该点开始的边。删除一条边,将其指向节点的入度减1,从而找到没有前驱节点的下一个顶点。3.重复以上直到输出最后一个顶点。如果还有顶点没有输出,说明有环!对于上图的简单序列,步骤可以简述为:step1:删除节点1(或2)及其指向的边,并输出节点step2:删除节点2(或3)及其指向的边指向,输出节点step2(此处两步):删除节点3(或4)及其指向的边,输出节点,再删除节点3(或6)其指向的边,将输出节点.step3:按照以上规则重复,直到删除所有节点。这样就完成了一个拓扑排序过程,得到了一个拓扑序列,但是这个序列不是唯一的。从算法执行过程来看,有多种选择。具体结果取决于你算法的设计,但是只要满足DAG图前后相对关系的要求即可。另外,观察12436578这个序列满足我们说的相关节点指向前面,the指向后面。如果完全没关系,也不一定是前后(比如1、2)代码实现对于拓扑排序,如何用代码实现呢?上面虽然详细介绍了思路和过程,也很容易理解,但实际上代码的实现还是很有必要考虑如何在空间和时间上取得更好的平衡和更好的效率?首先,考虑存储。对于节点,它们应该存储在邻接矩阵还是邻接表中?通常,如果使用矩阵存储,拓扑排序会更有效。稀疏,浪费内存空间,这里我们还是使用邻接表来存储节点。另外,如果图中的节点是按顺序编号的,比如1,2,3,4,5,6,我们可以直接用数组,但是如果遇到1,2,88,9999类似的不连续跨度,节点编号很大,也可以考虑使用Map存储来映射位置。那么我们具体的代码思路是:①新建一个节点类,包括节点值及其指向的节点集合(这里直接使用List集合)②初始化一个个人节点数组,输入/枚举节点之间的关系,将指向的节点录入学历+1!(例如A—>C),则C的入度为+1;③扫描所有节点(这里扫描数组)。将入度为0的所有点添加到容器堆栈(队列)中。④当栈(队列)不为空时,抛出其中任意一个节点(只要入度为零,可以随意选择顺序)。输出该节点,将该节点指向的所有节点的入度减1,如果某个点的入度减为0,则将其加入栈(队列)。⑤重复以上操作,直到栈(队列)为空。这里栈或队列主要用来存放入度为0的节点,只需要初始扫描表就可以将入度为0的节点放入栈(队列)中。因为节点之间存在关联,如果一个节点想要入度为0,那么它的前驱节点必须在它之前的入度为0。去掉关联的箭头,将自己的入度减1。在有向无环图中,总会有多个入度为0的节点。在具体实现上,有多种方法。这只是一个简单的演示,效率可能不会很高。你可以参考一下。具体实现代码为:importjava.util.ArrayDeque;importjava.util.ArrayList;importjava.util.List;importjava.util.Queue;importjava.util.Stack;publicclasstuopu{staticclassnode{intvalue;List
