在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
拓扑排序常用的两个方法
1、减治技术
- 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
- 从图中删除该顶点和所有以它为起点的有向边。
- 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
所以拓扑排序结果是1、2、4、3、5
通常,一个有向无环图可以有一个或多个拓扑排序序列。
2、基于DFS来实现
执行一次DFS遍历,并记住顶点变成死端(即退出遍历栈)的顺序,将该次序反过来就得到拓扑排序的一个解。当然在遍历时,不能遇到回边,如果遇到一条回边,该图就不是一个有向无环图,并且对它的顶点进行拓扑排序是不可能的。
当一个顶点v退出DFS栈时,在比v更早出栈的顶点中,不可能存在一个顶点u拥有一条边从u指向v,否则就构成了一个回边。所以在退栈次序的队列中,任何这样的顶点都会排在v的后面,并且在逆序中排在v的前面。
算法:
(1)减治思想的程序,维护一个类Graph,类中定义邻接表list,入度为0的集合,以及每个顶点的入度。
#include<iostream> #include <list> #include <vector> #include <queue> using namespace std; vector<vector<int> >sum; vector<int> temp; /************************类声明************************/ class Graph { int V; // 顶点个数 list<int> *adj; // 邻接表,图的表示方式 queue<int> q; // 维护一个入度为0的顶点的集合 int* indegree; // 记录每个顶点的入度 public: Graph(int V); // 构造函数 ~Graph(); // 析构函数 void addEdge(int v, int w); // 添加边 bool topological_sort(); // 拓扑排序 bool dfs(int u); }; /************************类定义************************/ Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; indegree = new int[V]; // 入度全部初始化为0 for(int i=0; i<V; ++i) indegree[i] = 0; } Graph::~Graph() { delete [] adj; delete [] indegree; } vector<int> c; void Graph::addEdge(int v, int w) { adj[v].push_back(w); ++indegree[w]; } bool Graph::topological_sort() { for(int i=0; i<V; ++i) if(indegree[i] == 0) q.push(i); // 将所有入度为0的顶点入队 int count = 0; // 计数,记录当前已经输出的顶点数 while(!q.empty()) { int v = q.front(); // 从队列中取出一个顶点 q.pop(); cout << v << " "; // 输出该顶点 ++count; // 将所有v指向的顶点的入度减1,并将入度减为0的顶点入栈 list<int>::iterator beg = adj[v].begin(); for( ; beg!=adj[v].end(); ++beg) if(!(--indegree[*beg])) q.push(*beg); // 若入度为0,则入栈 } if(count < V) return false; // 没有输出全部顶点,有向图中有回路 else return true; // 拓扑排序成功 } int main() { Graph g(6); // 创建图 g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); g.topological_sort(); return 0; }
(2)基于DFS的算法
#include<iostream> #include <list> #include <vector> #include <queue> using namespace std; vector<vector<int> >sum; vector<int> temp; /************************类声明************************/ class Graph { int V; // 顶点个数 list<int> *adj; // 邻接表 queue<int> q; // 维护一个入度为0的顶点的集合 int* indegree; // 记录每个顶点的入度 public: Graph(int V); // 构造函数 ~Graph(); // 析构函数 void addEdge(int v, int w); // 添加边 bool topological_sort(); // 拓扑排序 bool dfs(int u); void print(); }; /************************类定义************************/ Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; indegree = new int[V]; // 入度全部初始化为0 for(int i=0; i<V; ++i) indegree[i] = 0; } Graph::~Graph() { delete [] adj; delete [] indegree; } vector<int> c;//用来标记是否被访问 void Graph::addEdge(int v, int w) { adj[v].push_back(w); ++indegree[w]; } vector<int>TopNum;//用来存放最后的拓扑排序结果,最先返回的在最后 int t; bool Graph::dfs(int u)//从一个节点开始,找出dfs序列,返回该节点之后是否存在一个拓扑序列,只要之后遍历到的点和之前的点u有关系(v->u),那么就说明成环!返回false。 { c[u]=-1;//-1表示已经访问过,0表示未访问过 list<int>::iterator beg = adj[u].begin(); for( ; beg!=adj[u].end(); ++beg) { if(c[*beg]==-1) {//大水冲了龙王庙,这个i点在之前已经被遍历到了,成环! return false; } if(!c[*beg]&&!dfs(*beg)) return false; } //经过了检验 c[u]=1;//对应的c不能是-1,也不能是0 TopNum[--t]=u;//逆序存放 return true; } bool Graph::topological_sort() { int h=V; t=V; for(int i=0;i<h;i++) { c.push_back(0); TopNum.push_back(0); } for(int i=0;i<h;i++) if(!c[i]) if(!dfs(i)) return false; return true; } void Graph::print()//输出邻接表,为了检验是否完成图的构造 { for(int i=0;i<V;i++) { list<int>::iterator beg = adj[i].begin(); for( ; beg!=adj[i].end(); ++beg) { cout<<*beg<<'\0'; } } } int main() { Graph g(6); // 创建图 g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); //g.print(); cout<<g.topological_sort()<<endl; for(int i=0;i<TopNum.size();i++) { cout<<TopNum[i]<<'\0'; } return 0; }
另外一种dfs代码
#include<iostream> #include <list> #include <stack> using namespace std; class Graph { int V; // 顶点数 list<int> *adj;//指向邻接表 void topologicalSortUtil(int v, bool visited[], stack<int> &Stack); public: Graph(int V); // 构造函数 // 加入边 void addEdge(int v, int w); // 输出拓扑排序的一个序列 void topologicalSort(); }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); // Add w to v’s list. } // A recursive function used by topologicalSort void Graph::topologicalSortUtil(int v, bool visited[], stack<int> &Stack) { // 用来标记节点是否被访问 visited[v] = true; // Recur for all the vertices adjacent to this vertex list<int>::iterator i; for (i = adj[v].begin(); i != adj[v].end(); ++i) if (!visited[*i])//如果该节点没有被访问,则调用dfs函数 topologicalSortUtil(*i, visited, Stack); // 把当前节点打入栈中 Stack.push(v); } // 拓扑排序函数,用到了topologicalSortUtil函数 void Graph::topologicalSort() { stack<int> Stack; bool *visited = new bool[V]; for (int i = 0; i < V; i++) visited[i] = false;//初始化为false,表示未被访问过 // 挨个遍历节点,对每个节点进行dfs遍历 for (int i = 0; i < V; i++) if (visited[i] == false) topologicalSortUtil(i, visited, Stack); // Print contents of stack while (Stack.empty() == false) { cout << Stack.top() << " "; Stack.pop(); } } int main() { // Create a graph given in the above diagram Graph g(6); g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); cout << "Following is a Topological Sort of the given graph \n"; g.topologicalSort(); return 0; }
那如果要输出全部的路径呢,比如我需要知道图中所有可行的路径,上图中就是[5,2,3,1],[5,0],[4,0],[4,1]
我选择用dfs的方式,来输出全部的路径。就是找到入度为0的节点,因为所有的路径一定都是从这些节点出发的,然后依次找这些节点的深度优先遍历(在这些节点的邻接表中找),每遍历到一个节点,都将它打入一个vector中,再遍历该节点邻接的一个节点,直到没有后续的路之后(当前节点邻接表为空),将vector放入sum中,每次从入度为0的一个节点开始时,都将temp清零一下,因为这是全新的一个路径。如果到了一个顶点的邻接表尽头,意味着没有再和它相连的点了,那么temp要把该节点pop出来,以便存放另外一个路径。最后,sum的大小就是路径的个数,sum中的序列就是所有的路径。
#include<iostream> #include <list> #include <vector> #include <stack> using namespace std; class Graph { int V; list<int> *adj; bool topologicalSortUtil(int v, bool visited[], stack<int> &Stack); int* indegree; // 记录每个顶点的入度 public: Graph(int V); void addEdge(int v, int w); void topologicalSort(); void printall(); }; vector<vector<int> > sum; vector<int> temp; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; indegree = new int[V]; // 入度全部初始化为0 for(int i=0; i<V; ++i) indegree[i] = 0; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); ++indegree[w]; } void Graph::printall() { stack<int> Stack; bool *visited = new bool[V]; for (int i = 0; i < V; i++) visited[i] = false; for(int i=0;i<V;i++) { if(indegree[i]==0 && visited[i] == false) { if(topologicalSortUtil(i, visited, Stack)) { temp.clear(); continue; } return; } } } bool Graph::topologicalSortUtil(int v, bool visited[], stack<int> &Stack) { visited[v] = true; temp.push_back(v); // Recur for all the vertices adjacent to this vertex list<int>::iterator i; for (i = adj[v].begin(); i != adj[v].end(); ) { if (!visited[*i]) topologicalSortUtil(*i, visited, Stack); if(++i==adj[v].end()) { temp.pop_back(); } } if(adj[v].empty()) { sum.push_back(temp); temp.pop_back(); } visited[v]=false; // Push current vertex to stack which stores result Stack.push(v); return true; } int main() { // Create a graph given in the above diagram Graph g(6); g.addEdge(5, 2); g.addEdge(5, 0); g.addEdge(4, 0); g.addEdge(4, 1); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(5,4); //Graph g(5); // g.addEdge(1,4); // g.addEdge(0,1); // g.addEdge(0,2); // g.addEdge(3,4); g.printall(); cout<<sum.size()<<endl; for(int i=0;i<sum.size();i++) { for(int j=0;j<sum[i].size();j++) { cout<<sum[i][j]<<'\0'; } cout<<endl; } return 0; }