拓扑排序

一、有向无环图

  学习拓扑排序必须先要了解有向无环图(Directed Acyclic Graph,DAG)。有向无环图,顾名思义就是不存在环结构的有向图。从有向无环图的任意一个顶点出发,都没有办法回到该节点。

 

二、拓扑排序

  拓扑排序是关于DAG图最重要的应用之一。拓扑排序将DAG图排列成一个线性序列,使其满足下面的性质:

  若在DAG图E中存在边<u,v>,且u指向v,则在E的拓扑排序中,u一定在v的前面。

  拓扑排序非常适合用来确定存在依赖关系的事件的发生顺序。例如,上大学时的课程的安排就很适合用拓扑排序来实现。首先,将这些课程视为节点:

 

 

   很明显,有一些课程之间存在依赖关系,例如,学习Web应用需要先学习Java,学习机器学习需要先学习概率论,学习概率论又必须先学习微积分。而且这些依赖关系不能形成环(如A依赖B,B依赖C,C又依赖A),所以很明显,这些课程可以组成一个DAG图。

   对这个DAG图进行拓扑排序,我们就可以得到一个序列,以这个序列的先后关系安排的课程就不会发生冲突。

 

三、拓扑排序的原理与实现

  拓扑排序的原理其实非常简单,步骤如下:

  1. 选取一个没有前置依赖(即入度为0)的节点

  2. 选出该节点后,删除该节点和所有与它相关的边。

  3. 重复1,直到所有节点都被挑选

  注:如果存在多个节点入度为0,则任选一个节点

  拓扑排序有两种实现方式

  • 深度优先搜索
  • 广度优先搜索

  代码实现如下:

 1 /*
 2 拓扑排序的两种写法
 3 Author: WZQ 
 4 Date: 19:23 2021/12/8
 5 */
 6 #include<iostream>
 7 #include<list>
 8 #include<stack>
 9 #include<queue>
10 #include<vector>
11 #include<cstdlib>
12 using namespace std;
13 
14 class Graph{
15     int V;
16     vector<vector<int>>adj;
17 public:
18     Graph(int V);
19     void addEdge(int v, int w);
20     vector<int>getInDegree();     //获取图的入度
21     void topologicalSortDFS(int v, vector<bool>&visited, stack<int>&Stack);
22     void topologicalSortViaDFS();  // 使用DFS进行拓扑排序
23     void topologicalSortViaBFS(); // 使用BFS进行拓扑排序
24 };
25 
26 Graph::Graph(int V){
27     this->V = V;
28     adj.resize(V);
29 }
30 void Graph::addEdge(int v, int w){
31     adj[v].push_back(w);
32 }
33 
34 void Graph::topologicalSortDFS(int v,vector<bool>&visited,stack<int>&Stack){
35 
36     visited[v] = true;
37     for (auto u : adj[v]){
38         if(!visited[u]) topologicalSortDFS(u, visited, Stack);
39     }
40     Stack.push(v);
41 }
42 void Graph::topologicalSortViaDFS(){
43     vector<bool>visited(V,false);
44     stack<int>Stack;
45     for (int i = 0; i < V; i++){
46         if (!visited[i]) topologicalSortDFS(i,visited,Stack);
47     }
48     while (!Stack.empty()){
49         cout << Stack.top() << " ";
50         Stack.pop();
51     }
52     cout << endl;
53 }
54 vector<int>Graph::getInDegree(){
55     vector<int>inDegree(V,0);
56     for (int i = 0; i < V; i++){
57         for (auto j : adj[i]){
58             inDegree[j]++;
59         }
60     }
61     return inDegree;
62 }
63 void Graph::topologicalSortViaBFS(){
64 
65     vector<int>inDegree = getInDegree();
66     queue<int>Queue;
67     for (int i = 0; i < V; i++){
68         if (inDegree[i] == 0)
69             Queue.push(i);
70     }
71     while (!Queue.empty()){
72         int u = Queue.front();
73         cout << u << " ";
74         Queue.pop();
75         for (auto v : adj[u]){
76             inDegree[v]--;
77             if (!inDegree[v]) Queue.push(v);
78         }
79     }
80     cout << endl;
81 }
82 
83 int main(){
84     Graph G(6);
85 
86     G.addEdge(5,2);
87     G.addEdge(5,0);
88     G.addEdge(4,0);
89     G.addEdge(4,1);
90     G.addEdge(2,3);
91     G.addEdge(3,1);
92     G.topologicalSortViaDFS();
93     G.topologicalSortViaBFS();
94     system("pause");
95     return 0;
96 }

代码的执行结果显示,基于DFS和基于BFS的写法给出了不同的拓扑排序结果,但没有问题,显然两个结果都是合理的。