基于有向图的排序方式 - 拓扑排序
一、拓扑排序的概念
在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。例如下图:

为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:

这样当某个学生要学习这些课程的时候,需要给出一个学习的方案,我们只需要对图中的顶点进行排序,让它转换为一个线性序列,就可以解决问题。这时,我们就需要使用 拓扑排序 的算法。
拓扑排序的意思就是:给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素,此时我们就可以明确表示出每一个顶点的优先级。下图就是一幅拓扑排序后的示意图:


二、检测有向图中的环
拓扑排序的前提是 必须保证图中没有环的存在,那么如何检测有向环呢?下面的代码是相应的解决方案。
package com.renhui.graph; /** * 判断图中是否有环 */ public class DirectedCycle { // 索引代表顶点,值表示顶点是否被搜索过 private boolean[] marked; // 记录图中是否有环 private boolean hasCycle; // 索引代表顶点,使用栈的思想,记录当前顶点没有已经处于正在搜索的有向路径上。 private boolean[] onStack; private DirectedCycle(Digraph digraph) { marked = new boolean[digraph.V()]; onStack = new boolean[digraph.V()]; this.hasCycle = false; for (int v = 0; v < digraph.V(); v++) { if (!marked[v]) { dfs(digraph, v); } } } private void dfs(Digraph digraph, int v) { marked[v] = true; onStack[v] = true; for (Integer w : digraph.adj(v)) { if (!marked[w]) { dfs(digraph, w); } if (onStack[w]) { hasCycle = true; return; } } onStack[v] = false; } public boolean hasCycle() { return hasCycle; } public static void main(String[] args) { Digraph digraph = new Digraph(5); digraph.addEdge(3, 0); digraph.addEdge(0, 2); digraph.addEdge(2, 1); digraph.addEdge(1, 0); digraph.addEdge(1, 4); DirectedCycle directedCycle = new DirectedCycle(digraph); boolean isHasCycle = directedCycle.hasCycle(); System.out.println("当前图是否存在环:" + isHasCycle); } }
三、拓扑排序实现
首先我们需要知道拓扑排序的核心:顶点排序。通过一个栈来存储顶点,当我们深度搜索图的时候,每搜索完一个顶点,就把该顶点放入到reversePost中,这样就可以实现顶点排序。
顶点排序的实现:
package com.renhui.graph; import java.util.Stack; /** * 顶点排序 */ public class DepthFirstOrder { private boolean[] marked; // 索引代表顶点,值表示当前顶点是否已经被搜索 private Stack<Integer> reversePost; // 使用栈,存储顶点序列 // 创建一个顶点排序对象,生成顶点线性序列 public DepthFirstOrder(Digraph digraph) { this.marked = new boolean[digraph.V()]; this.reversePost = new Stack<>(); // 遍历图中的每一个顶点,让每个顶点作为入口,完成一次深度优先搜索 for (int v = 0; v < digraph.V() ; v++) { if (!marked[v]) { dfs(digraph, v); } } } // 基于深度优先搜索,生成顶点线性序列 private void dfs(Digraph digraph, int v) { marked[v] = true; for (Integer w : digraph.adj(v)) { if (!marked[w]) { dfs(digraph, w); } } reversePost.push(v); } // 获取顶点线性序列 public Stack<Integer> reversePost() { return reversePost; } }
有了顶点排序和之前的环的检测,我们就可以很简单的实现拓扑排序了:
即:先检测有没有环,如果没有环,就调用顶点排序即可。

浙公网安备 33010602011771号