一 引言
玩过塞尔达的小伙伴都知道,要想救公主,要经历以下的步骤(当然也可以拿到滑翔伞直接打boss........),其中拔出大师之剑不光要解锁迷雾森林,还要解锁神庙增加心之容器,也就是说两个条件必须同时满足,之后再解放四神兽,击败盖侬救出公主
可以看出任务之间有先后顺序的,图中的任务有的难,有的简单,但是不重要,拓扑只考虑它们之间的先后依赖关系,而不考虑它们自身的属性,这也是为什么叫做拓扑排序的原因。拓扑排序是指对于一个有向无环图(DAG, Directed Acyclic Graph),找到一种线性排列的方式,使得对于每一条从顶点u到v的有向边(u, v),u在排序结果中都出现在v之前。换句话说,它是一种将图中的节点按某种顺序排列的方法,以确保所有的依赖关系都能得到满足。
为什么必须是无环图?
拓扑排序要求对于每条有向边 (u, v),顶点 u 必须出现在顶点 v 之前。如果图中存在环,则环内的所有顶点都相互依赖,没有一个明确的起点或终点可以开始排序。换句话说,在环内不存在任何一个顶点可以在另一个顶点之前处理。
二 应用
拓扑排序在许多领域都有广泛的应用,尤其是在处理依赖关系方面:
1 任务调度:例如项目管理、构建系统中的任务依赖解析。
2 编译器优化:确定代码模块或函数之间的调用顺序。
3 网络路由中的优先级处理等领域
4 包管理器中的依赖解析:如npm、Maven等工具中软件包安装顺序的确定。
5 课程安排:规划学习路径,确保先修课程被正确安排在后续课程之前。
6 数据库迁移脚本生成:保证表创建、修改等操作按照正确的依赖顺序执行。
7 游戏开发:比如资源加载顺序、关卡解锁条件、技能树等。
三 实现
Kahn算法
1 思路
既然是处理依赖关系,那么就可以从入度为0的节点开始,像剥洋葱一样一层一层深入到最里层。比如对于开头说的塞尔达,
(1) 先找到所有入度(即指向该节点的边的数量)为0的任务,发现是解锁神庙和解锁迷雾森林
(2) 一个一个处理入度为0的任务,处理的方式就是将入度为0的任务和它的边删除,同时将它指向的任务的入度减去1
(3) 解锁神庙处理完以后,入度为0的任务只剩下解锁迷雾森林,同时拿到大师之剑的任务的入度变成1。再处理解锁迷雾森林后,拿到大师之剑的入度变成0,这时候就该处理拿到大师之剑的任务。
(4)依次类推,直到救出塞尔达的入度为0,处理完成。通过迭代地移除入度为0的节点来构建排序结果被称作Kahn算法。
2 步骤
1 计算每个节点的入度
2 初始化一个队列,包含所有入度为0的节点。
3 从队列中取出一个节点,将其加入拓扑排序的结果列表,并减少其所有邻接节点的入度。
4 如果某个邻接节点的入度变为0,则将其加入队列。
5 重复步骤3-4直到队列为空。
6 如果结果列表中的节点数量等于原始图中的节点数,则返回结果;否则,图中存在环。<