拓扑排序
一、有向无环图
学习拓扑排序必须先要了解有向无环图(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的写法给出了不同的拓扑排序结果,但没有问题,显然两个结果都是合理的。