1.定义

在现实生活中,我们经常会同一时间接到很多任务去完成,但是这些任务的完成是有先后次序的。以我们学习java 学科为例,我们需要学习很多知识,但是这些知识在学习的过程中是需要按照先后次序来完成的。从java基础,到 jsp/servlet,到ssm,到springboot等是个循序渐进且有依赖的过程。在学习jsp前要首先掌握java基础和html基 础,学习ssm框架前要掌握jsp/servlet之类才行。
image.png
为了简化问题,我们使用整数为顶点编号的标准模型来表示这个案例:
image.png

此时如果某个同学要学习这些课程,就需要指定出一个学习的方案,我们只需要对图中的顶点进行排序,让它转换 为一个线性序列,就可以解决问题,这时就需要用到一种叫拓扑排序的算法。

拓扑排序:
给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素,此时就可以明 确的表示出每个顶点的优先级。下列是一副拓扑排序后的示意图:
image.png

2.检测有向图中的环

如果学习x课程前必须先学习y课程,学习y课程前必须先学习z课程,学习z课程前必须先学习x课程,那么一定是有 问题了,我们就没有办法学习了,因为这三个条件没有办法同时满足。其实这三门课程x、y、z的条件组成了一个环:
image.png

实现:
在API中添加了onStack[] 布尔数组,索引为图的顶点,当我们深度搜索时:
1. 在如果当前顶点正在搜索,则把对应的onStack数组中的值改为true,标识进栈;
2. 如果当前顶点搜索完毕,则把对应的onStack数组中的值改为false,标识出栈;
3. 如果即将要搜索某个顶点,但该顶点已经在栈中,则图中有环;

image.png

代码实现:

  1. package 图;
  2. public class DirectedCycle {
  3. //索引代表顶点,值表示当前顶点是否已经被搜索
  4. private boolean[] marked;
  5. //记录图中是否有环
  6. private boolean hasCycle;
  7. //索引代表顶点,使用栈的思想,记录当前顶点有没有已经处于正在搜索的有向路径上
  8. private boolean[] onStack;
  9. //创建一个检测环对象,检测图G中是否有环
  10. public DirectedCycle(Digraph G) {
  11. //创建一个和图的顶点数一样大小的marked数组
  12. marked = new boolean[G.V()];
  13. //创建一个和图的顶点数一样大小的onStack数组
  14. onStack = new boolean[G.V()];
  15. //默认没有环
  16. this.hasCycle = false;
  17. //遍历搜索图中的每一个顶点
  18. for (int v = 0; v < G.V(); v++) {
  19. //如果当前顶点没有搜索过,则搜索
  20. if (!marked[v]) {
  21. dfs(G, v);
  22. }
  23. }
  24. }
  25. //基于深度优先搜索,检测图G中是否有环
  26. private void dfs(Digraph G, int v) {
  27. //把当前顶点标记为已搜索
  28. marked[v] = true;
  29. //让当前顶点进栈
  30. onStack[v] = true;
  31. //遍历v顶点的邻接表,得到每一个顶点w
  32. for (Integer w : G.adj(v)) {
  33. //如果当前顶点w没有被搜索过,则递归搜索与w顶点相通的其他顶点
  34. if (!marked[w]) {
  35. dfs(G, w);
  36. }
  37. //如果顶点w已经被搜索过,则查看顶点w是否在栈中,如果在,则证明图中有环,修改hasCycle标记,结束循环
  38. if (onStack[w]) {
  39. hasCycle = true;
  40. return;
  41. }
  42. }
  43. //当前顶点已经搜索完毕,让当前顶点出栈
  44. onStack[v] = false;
  45. }
  46. //判断w顶点与s顶点是否相通
  47. public boolean hasCycle() {
  48. return hasCycle;
  49. }
  50. }

3. 基于深度优先的顶点排序

实现:
在API的设计中,我们添加了一个栈reversePost用来存储顶点,当我们深度搜索图时,每搜索完毕一个顶点,把该 顶点放入到reversePost中,这样就可以实现顶点排序。
image.png
image.png

代码实现

  1. package 图;
  2. import 线性表.Stack;
  3. public class DepthFirstOrder {
  4. //索引代表顶点,值表示当前顶点是否已经被搜索
  5. private boolean[] marked;
  6. //使用栈,存储顶点序列
  7. private Stack<Integer> reversePost;
  8. //创建一个检测环对象,检测图G中是否有环
  9. public DepthFirstOrder(Digraph G) {
  10. //创建一个和图的顶点数一样大小的marked数组
  11. marked = new boolean[G.V()];
  12. reversePost = new Stack<Integer>();
  13. //遍历搜索图中的每一个顶点
  14. for (int v = 0; v < G.V(); v++) {
  15. //如果当前顶点没有搜索过,则搜索
  16. if (!marked[v]) {
  17. dfs(G, v);
  18. }
  19. }
  20. }
  21. //基于深度优先搜索,检测图G中是否有环
  22. private void dfs(Digraph G, int v) {
  23. //把当前顶点标记为已搜索
  24. marked[v] = true;
  25. //遍历v顶点的邻接表,得到每一个顶点w
  26. for (Integer w : G.adj(v)) {
  27. //如果当前顶点w没有被搜索过,则递归搜索与w顶点相通的其他顶点
  28. if (!marked[w]) {
  29. dfs(G, w);
  30. }
  31. }
  32. //当前顶点已经搜索完毕,让当前顶点入栈
  33. reversePost.push(v);
  34. }
  35. //获取顶点线性序列
  36. public Stack<Integer> reversePost() {
  37. return reversePost;
  38. }
  39. }

4. 拓扑排序实现

前面已经实现了环的检测以及顶点排序,那么拓扑排序就很简单了,基于一幅图,先检测有没有环,如果没有环, 则调用顶点排序即可。

代码实现:

  1. package 图;
  2. import 线性表.Stack;
  3. public class TopoLogical {
  4. //顶点的拓扑排序
  5. private Stack<Integer> order;
  6. //构造拓扑排序对象
  7. public TopoLogical(Digraph G) {
  8. //创建检测环对象,检测图G中是否有环
  9. DirectedCycle dCycle = new DirectedCycle(G);
  10. if (!dCycle.hasCycle()) {
  11. //如果没有环,创建顶点排序对象,进行顶点排序
  12. DepthFirstOrder depthFirstOrder = new DepthFirstOrder(G);
  13. order = depthFirstOrder.reversePost();
  14. }
  15. }
  16. //判断图G是否有环
  17. private boolean isCycle() {
  18. return order == null;
  19. }
  20. //获取拓扑排序的所有顶点
  21. public Stack<Integer> order() {
  22. return order;
  23. }
  24. }