转载来自点击打开链接
以下将文章的一些内容粘贴过来以方便自己查看,请尊重原创,转载请注明
假设我们有一组任务要完成,并且有些任务要在其它任务完成之后才能开始,所以我们必须非常小心这些任务的执行顺序。
如果这些任务的执行顺序足够简单的话,我们可以用链表来存储它们,这是一个很好的方案,让我们可以准确知道任务的执行顺序。问题是有时候不同任务之间的关系是非常复杂的,有些任务依赖于两个甚至更多的任务,或者反过来很多任务依赖自己。
因此我们不能通过链表或者树的数据结构来对这个问题建模。对这类问题唯一合理的数据结构就是图。我们需要哪种图呢?很显然,我们需要有向图来描述这种关系,而且是不能循环的有向图,我们称之为有向无环图。要通过拓扑排序对图形进行排序,这些图必须是不能循环和有向的。为什么这些图不能循环呢?答案很明显,如果图形是循环的,我们无法知道哪个任务该优先执行,也不可能对任务进行排序。现在我们一要做的是对图中的每个节点排序,组成一条条边(u,v),u在v之前执行。然后我们就可以得到所有任务的线性顺序,并按这种顺序执行任务就一切都OK了。
例如,下面的图的一个拓扑排序是“5 4 2 3 1 0”。一个图可以有多个拓扑排序。
另一个拓扑排序是“4 5 2 3 1 0”。拓扑排序的第一个顶点总是入度为0。
方法一
现在我们可以得到这个算法的基本步骤:
1 | 1.构造空列表 L和S; |
2 | 2.把所有没有依赖节点(入度为0)的节点放入L; |
3 | 3.当L还有节点的时候,执行下面步骤: |
4 | 3.1 L中拿出一个节点n(从L中remove掉),并放入S |
5 | 3.2 对每一个邻近n的节点m, |
6 | 3.2.1 去掉边(n,m);(表示加入最终结果集S) |
7 | 3.2.2 如果m没有依赖节点(入度为零),把m放入L; |
核心就是:每次都选取入度为0的节点,再更新其相邻的节点的入度 。
这个是相对比较直观的算法,也是常见的一种算法。我们用一个数组degree[]记录所有顶点的入度。删除点时更新该数组。
参考下面代码函数:topologicalSort1()
方法二
另外一种方法是参考DFS,对图的深度优先遍历做些修改。我们确信在有向图中如果存在一条边(u,v),那么顶点u会先于顶点v进入列表中。因此在深度遍历时,用栈来存储遍历的顺序。参考下面代码的函数:topologicalSort2()
C++实现
001 | // C++实现的拓扑排序算法 |
002 | #include<iostream> |
003 | #include <list> |
004 | #include <stack> |
005 | using namespace std; |
006 |
007 | // 图类 |
008 | class Graph |
009 | { |
010 | int V; //顶点个数 |
011 |
012 | // 邻接表 |
013 | list< int > *adj; |
014 | // 拓扑排序方法 2的辅助函数 |
015 | void topologicalSortRecall( int v, bool visited[], stack< int > &Stack); |
016 |
017 | public : |
018 | Graph( int V); |
019 |
020 | // 添加边 |
021 | void addEdge( int v, int w); |
022 |
023 | //拓扑排序普通方法 |
024 | void topologicalSort1(); |
025 |
026 | // 拓扑排序方法二 |
027 | void topologicalSort2(); |
028 | }; |
029 |
030 | Graph::Graph( int V) |
031 | { |
032 | this ->V = V; |
033 | adj = new list< int >[V]; |
034 | } |
035 |
036 | void Graph::addEdge( int v, int w) |
037 | { |
038 | adj[v].push_back(w); // |
039 | } |
040 |
041 | //类似深度优先遍历,将和V相邻的顶点(且为访问过的)放入栈中 |
042 | void Graph::topologicalSortRecall( int v, bool visited[], stack< int > &stk) |
043 | { |
044 | //标记v为访问过的 |
045 | visited[v] = true ; |
046 |
047 | // 对每个顶点进行递归调用 |
048 | list< int >::iterator i; |
049 | for (i = adj[v].begin(); i != adj[v].end(); ++i) |
050 | if (!visited[*i]) |
051 | topologicalSortRecall(*i, visited, stk); |
052 |
053 | // 保存顶点 |
054 | stk.push(v); |
055 | } |
056 |
057 | // 方法二,使用递归调用实现拓扑排序 |
058 | void Graph::topologicalSort2() |
059 | { |
060 | stack< int > stk; |
061 | bool *visited = new bool [V]; |
062 | for ( int i = 0; i < V; i++) |
063 | visited[i] = false ; |
064 |
065 | //每个顶点都调用一次 |
066 | for ( int i = 0; i < V; i++) |
067 | if (visited[i] == false ) |
068 | topologicalSortRecall(i, visited, stk); |
069 |
070 | // 打印 |
071 | while (stk.empty() == false ) |
072 | { |
073 | cout << stk.top() << " " ; |
074 | stk.pop(); |
075 | } |
076 | } |
077 |
078 | // 方法一 |
079 | void Graph::topologicalSort1() |
080 | { |
081 | list< int >::iterator j; |
082 | int degree[V]; |
083 | //遍历所有的边,计算入度 |
084 | for ( int i=0; i<V; i++){ |
085 | degree[i] = 0; |
086 | for (j = adj[i].begin(); j != adj[i].end(); ++j){ |
087 | degree[*j]++; |
088 | } |
089 | } |
090 | list< int > zeroNodes; //所有入度为0的点 |
091 | list< int > result; //所有入度为0的点 |
092 | for ( int i=0; i<V; i++){ |
093 | if (degree[i] == 0){ |
094 | zeroNodes.push_back(i); |
095 | } |
096 | } |
097 | while (zeroNodes.size() > 0){ |
098 | int top = zeroNodes.back(); |
099 | zeroNodes.pop_back(); |
100 | result.push_back(top); |
101 | for (j = adj[top].begin(); j != adj[top].end(); ++j){ |
102 | degree[*j]--; //删除和top相邻的边,并更新其它顶点的入度 |
103 | if (degree[*j] == 0) zeroNodes.push_back(*j); |
104 | } |
105 | } |
106 |
107 | //打印结果 |
108 | for (j= result.begin(); j != result.end(); j++) |
109 | cout << (*j) << " " ; |
110 | } |
111 |
112 | int main() |
113 | { |
114 | // 创建文中所以的图 |
115 | Graph g(6); |
116 | g.addEdge(5, 2); |
117 | g.addEdge(5, 0); |
118 | g.addEdge(4, 0); |
119 | g.addEdge(4, 1); |
120 | g.addEdge(2, 3); |
121 | g.addEdge(3, 1); |
122 |
123 | cout << "Following is a Topological Sort of the given graph using topologicalSort1\n" ; |
124 | g.topologicalSort1(); |
125 | cout << endl; |
126 | cout << "Following is a Topological Sort of the given graph using topologicalSort2\n" ; |
127 | g.topologicalSort2(); |
128 | return 0; |
129 | } |