其他分享
首页 > 其他分享> > 拓扑排序

拓扑排序

作者:互联网

日常项目开发中,一般流程是产品经理提出需求,相关人员进行需求评审,然后是前后端工程师开发功能,再到测试、发布上线。

流程如下:

                                                                  图-1

可以看到,这些步骤是存在先后(依赖)关系的:前后端的开发依赖于需求评审,测试又依赖前后端开发。不可能需求还没评审,工程师就开始开发。

其次,这些步骤之间不能存在相互依赖,例如需求评审又依赖测试,这样就没法执行下去了。

                                      图-2

我们把满足这些特性的流程称为AOV网(Activity On Vertex Network),步骤可以看作是一个个活动(activity),用图的顶点(vertex)来表示。对应到图的数据结构,是一张带有方向的且无环(无相互依赖)的有向无环图(Directed Acyclic Graph),简称DAG。

* AOE网是用边来表示活动(Activity On Edge Network)

根据这种有向无环图(DAG)生成的各顶点(活动)的依赖序列,我们称为拓扑排序(Topologial Sort)。

可以看到这里的排序是基于依赖关系的,不同于之前介绍的冒泡、快速排序等这种基于大小的排序。而拓扑的意思,根据Kahn发表的“Topological Sorting of Large Networks”一文,应该是解决计算机网络结构中的“拓扑”,倾向于计算机网络中表示各个网络节点关系的拓扑图。

 

下面介绍两种拓扑排序的算法
一、卡恩算法(Kahn's algorithm)

该方法理解起来比较简单,主要流程是先计算出每个顶点依赖的顶点个数,或者叫顶点的入度(indegree),从没有依赖其他顶点的顶点a(即入度为0的顶点)开始处理,处理完毕同时把依赖此顶点a的对应顶点数量减一。之后再计算出一个没有依赖的顶点,再把对应顶点数量减一,直到处理完全部顶点。

二、深度优先搜索(Depth First Search)

深度优先搜索只是一个单纯的遍历,怎么会满足拓扑排序呢?我们先回顾下深度优先搜索的流程,如图-3

                                                      图-3

可以看到深度优先是优先往一条路径的深层搜索,当到达最底层顶点c后,再回到上一层b,访问b对应的d,之后再回到b,由于b对应的顶点都已经访问过,则回溯至a,a还有未访问的顶点e,e再下探至f。第一次到达最底层的顶点c,一定是此路径的终点,从依赖的角度看,必然为依赖的最后一步。当本层顶点全部遍历完毕后,才会回到上层顶点,相当于以上层顶点为依赖的顶点已经遍历完成。如图-3中,b指向的c d,即c d依赖b。按照c d b访问顺序存储,再按照倒序的b d c输出,就实现了依赖的排序。

可见实现了图的深度优先搜索,也就得到了拓扑排序的结果。不过要优先保存最底层的顶点,且需要考虑环的问题。

 

以下为两种算法的实现代码和输出说明,演示图数据如下。

                                                  图-4

图-4演示数据参考于https://www.bilibili.com/video/BV1uf4y1U7DX

  1 import java.util.*;
  2 
  3 public class TopologicalSort {
  4     private int vertexNum;
  5     private List<Integer>[] graph;
  6 
  7     private boolean[] visited;
  8     private boolean[] search;
  9     private Stack<Integer> dfsSort;
 10 
 11     public static void main(String[] args) {
 12         TopologicalSort tsk = new TopologicalSort();
 13         tsk.initDemo();
 14         tsk.print();
 15 
 16         System.out.println("[Kahn]");
 17         tsk.kahn();
 18         System.out.println("[DFS]");
 19         tsk.dfs();
 20     }
 21 
 22     public void kahn() {
 23         //计算每个顶点的入度(即指向此顶点的边的数量,也就是依赖的顶点数)
 24         int[] indegree = new int[vertexNum];
 25         for (int i = 0; i < vertexNum; i++) {
 26             for (int vertex : graph[i]) {
 27                 indegree[vertex]++;
 28             }
 29         }
 30         //初始入度为0的顶点
 31         Queue<Integer> queue = new LinkedList<>();
 32         for (int i = 0; i < vertexNum; i++) {
 33             if (indegree[i] == 0) {
 34                 queue.add(i);
 35             }
 36         }
 37         System.out.println("indegree " + Arrays.toString(indegree) + " queue " + queue);
 38 
 39         //排序结果
 40         List<Integer> sort = new ArrayList<>();
 41         while (!queue.isEmpty()) {
 42             //取出一个入度为0的顶点
 43             int zero = queue.poll();
 44             //并保存到排序结果中
 45             sort.add(zero);
 46             System.out.println("zero " + zero + " adjacent " + graph[zero]);
 47             //依赖此顶点的顶点入度减一(即以此顶点开始的边)
 48             for (int adjacent : graph[zero]) {
 49                 indegree[adjacent]--;
 50                 if (indegree[adjacent] == 0) {
 51                     System.out.println("enqueue <- " + adjacent);
 52                     queue.add(adjacent);
 53                 }
 54             }
 55             System.out.println("indegree " + Arrays.toString(indegree) + " queue " + queue);
 56         }
 57         //排序结果集个数小于顶点数量,说明存在环(即顶点的入度都大于0)
 58         if (sort.size() < vertexNum) {
 59             System.out.println("cycle in the graph!");
 60         } else {
 61             System.out.println(sort);
 62         }
 63     }
 64 
 65     public void dfs() {
 66         //保存顶点是否访问过
 67         visited = new boolean[vertexNum];
 68         //保存顶点是否正在搜索,用于环的判断
 69         search = new boolean[vertexNum];
 70         //排序结果
 71         dfsSort = new Stack<>();
 72 
 73         //顶点依次遍历
 74         for (int v = 0; v < vertexNum; v++) {
 75             if (!visited[v]) {
 76                 traversal(v);
 77             }
 78         }
 79         //排序结果输出
 80         while (!dfsSort.empty()) {
 81             System.out.print(dfsSort.pop() + " ");
 82         }
 83     }
 84 
 85     private void traversal(int vertex) {
 86         //标记为已访问
 87         visited[vertex] = true;
 88         //标记为搜索中
 89         search[vertex] = true;
 90         System.out.printf("%s start-> adjacent %s visited %s dfsSort %s search %s\n", vertex, graph[vertex], Arrays.toString(visited), dfsSort, Arrays.toString(search));
 91 
 92         for (int i : graph[vertex]) {
 93             //环的存在会导致顶点处于搜索状态时再次搜索
 94             if (search[i]) {
 95                 throw new RuntimeException("vertex " + i + ", cycle in the graph!");
 96             }
 97             if (!visited[i]) {
 98                 traversal(i);
 99             } else {
100                 System.out.println("skip " + i);
101             }
102         }
103         //标记为搜索结束
104         search[vertex] = false;
105         //搜索结束同时添加到结果集
106         dfsSort.add(vertex);
107         System.out.printf("%s <-end   adjacent %s visited %s dfsSort %s search %s\n", vertex, graph[vertex], Arrays.toString(visited), dfsSort, Arrays.toString(search));
108     }
109 
110     public void print() {
111         System.out.println("[graph]");
112         int index = 0;
113         for (List<Integer> arrayList : graph) {
114             System.out.println(index + " " + arrayList);
115             index++;
116         }
117     }
118 
119     public void initDemo() {
120         this.vertexNum = 9;
121         graph = new ArrayList[vertexNum];
122         for (int i = 0; i < vertexNum; i++) {
123             graph[i] = new ArrayList<>();
124         }
125         addEdge(0, 2, 7);
126         addEdge(1, 2, 3, 4);
127         addEdge(2, 3);
128         addEdge(3, 5, 6);
129         addEdge(4, 5);
130         addEdge(7, 8);
131         addEdge(8, 6);
132         //cyclic
133         //addEdge(5, 1);
134     }
135 
136     private void addEdge(int a, int... b) {
137         for (int i : b) {
138             graph[a].add(i);
139         }
140     }
141 }

 输出

[graph]
0 [2, 7]
1 [2, 3, 4]
2 [3]
3 [5, 6]
4 [5]
5 []
6 []
7 [8]
8 [6]
[Kahn]
indegree [0, 0, 2, 2, 1, 2, 2, 1, 1] queue [0, 1]
zero 0 adjacent [2, 7]
enqueue <- 7
indegree [0, 0, 1, 2, 1, 2, 2, 0, 1] queue [1, 7]
zero 1 adjacent [2, 3, 4]
enqueue <- 2
enqueue <- 4
indegree [0, 0, 0, 1, 0, 2, 2, 0, 1] queue [7, 2, 4]
zero 7 adjacent [8]
enqueue <- 8
indegree [0, 0, 0, 1, 0, 2, 2, 0, 0] queue [2, 4, 8]
zero 2 adjacent [3]
enqueue <- 3
indegree [0, 0, 0, 0, 0, 2, 2, 0, 0] queue [4, 8, 3]
zero 4 adjacent [5]
indegree [0, 0, 0, 0, 0, 1, 2, 0, 0] queue [8, 3]
zero 8 adjacent [6]
indegree [0, 0, 0, 0, 0, 1, 1, 0, 0] queue [3]
zero 3 adjacent [5, 6]
enqueue <- 5
enqueue <- 6
indegree [0, 0, 0, 0, 0, 0, 0, 0, 0] queue [5, 6]
zero 5 adjacent []
indegree [0, 0, 0, 0, 0, 0, 0, 0, 0] queue [6]
zero 6 adjacent []
indegree [0, 0, 0, 0, 0, 0, 0, 0, 0] queue []
[0, 1, 7, 2, 4, 8, 3, 5, 6]
[DFS]
0 start-> adjacent [2, 7] visited [true, false, false, false, false, false, false, false, false] dfsSort [] search [true, false, false, false, false, false, false, false, false]
2 start-> adjacent [3] visited [true, false, true, false, false, false, false, false, false] dfsSort [] search [true, false, true, false, false, false, false, false, false]
3 start-> adjacent [5, 6] visited [true, false, true, true, false, false, false, false, false] dfsSort [] search [true, false, true, true, false, false, false, false, false]
5 start-> adjacent [] visited [true, false, true, true, false, true, false, false, false] dfsSort [] search [true, false, true, true, false, true, false, false, false]
5 <-end   adjacent [] visited [true, false, true, true, false, true, false, false, false] dfsSort [5] search [true, false, true, true, false, false, false, false, false]
6 start-> adjacent [] visited [true, false, true, true, false, true, true, false, false] dfsSort [5] search [true, false, true, true, false, false, true, false, false]
6 <-end   adjacent [] visited [true, false, true, true, false, true, true, false, false] dfsSort [5, 6] search [true, false, true, true, false, false, false, false, false]
3 <-end   adjacent [5, 6] visited [true, false, true, true, false, true, true, false, false] dfsSort [5, 6, 3] search [true, false, true, false, false, false, false, false, false]
2 <-end   adjacent [3] visited [true, false, true, true, false, true, true, false, false] dfsSort [5, 6, 3, 2] search [true, false, false, false, false, false, false, false, false]
7 start-> adjacent [8] visited [true, false, true, true, false, true, true, true, false] dfsSort [5, 6, 3, 2] search [true, false, false, false, false, false, false, true, false]
8 start-> adjacent [6] visited [true, false, true, true, false, true, true, true, true] dfsSort [5, 6, 3, 2] search [true, false, false, false, false, false, false, true, true]
skip 6
8 <-end   adjacent [6] visited [true, false, true, true, false, true, true, true, true] dfsSort [5, 6, 3, 2, 8] search [true, false, false, false, false, false, false, true, false]
7 <-end   adjacent [8] visited [true, false, true, true, false, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7] search [true, false, false, false, false, false, false, false, false]
0 <-end   adjacent [2, 7] visited [true, false, true, true, false, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7, 0] search [false, false, false, false, false, false, false, false, false]
1 start-> adjacent [2, 3, 4] visited [true, true, true, true, false, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7, 0] search [false, true, false, false, false, false, false, false, false]
skip 2
skip 3
4 start-> adjacent [5] visited [true, true, true, true, true, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7, 0] search [false, true, false, false, true, false, false, false, false]
skip 5
4 <-end   adjacent [5] visited [true, true, true, true, true, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7, 0, 4] search [false, true, false, false, false, false, false, false, false]
1 <-end   adjacent [2, 3, 4] visited [true, true, true, true, true, true, true, true, true] dfsSort [5, 6, 3, 2, 8, 7, 0, 4, 1] search [false, false, false, false, false, false, false, false, false]
1 4 0 7 8 2 3 6 5

卡恩算法示意图-5

                                          图-5

深度优先搜索算法示意图-6

                                                  图-6

可以看到两种算法对环的检测机制是不同的,卡恩算法是通过计算每个顶点的入度,入度为0即不存在依赖,如果最终仍然存在入度不是0的节点,必然存在环。

深度优先搜索算法中,由于是递归调用,顶点开始搜索时把状态标记为搜索中,一直到本次递归结束后才会更新为搜索结束,遍历期间如果这个开始顶点再次出现,则说明必然存在环。

如图-7,当顶点1开始后被标记为搜索中,到达4,再到达5,而5存在指向1的边,再到达顶点1时,发现1的状态已经时搜索中了,说明这里出现了环。也就是我们需要区分出正常的回溯和环导致的重新访问。

                                               图-7

递归方式(代码的98行traversal方法)乍一看可能不太容易理解,看一下算法的输出内容图-8就很容易明白了,这里我增加了缩进,方便观察层次关系。可以认为是大循环里套了小循环,等小循环完毕大循环才能结束。0可以认为是最外侧的大循环,里面又开始了2、7,2里面又开始了3,3里面又开始了5、6……但不管小循环如何再嵌套,只有全部完成后大循环才能结束。

                                                                              图-8

拓扑的扩展阅读

“拓扑”到底是什么?

拓扑-数学术语

 

参考资料

Topological Sort

图的拓扑排序算法-视频

Kahn, Arthur B."Topological sorting of large networks"

Topological Sorting using Depth First Search (DFS)

Topological Sort using Breadth First Search (BFS)

标签:false,拓扑,dfsSort,adjacent,visited,顶点,排序,true
来源: https://www.cnblogs.com/binary220615/p/16584803.html