拓扑排序
拓扑排序的两个算法:
1. DFS
执行一次DFS遍历,并记住顶点变成死端(即退出遍历栈)的顺序。将该顺序反过来就得到了拓扑排序的一个解。当然,在遍历的时候不能遇到回边。如果遇到一条回边,该图就不是无环有向图,并且对它顶点的拓扑排序是不可能的。
难点:
Q:如何实现“将该顺序反过来”?
A:利用双端队列
#include <deque> deque <int> //in dfs(): tp.push_front(u); //output: while(!tp.empty()){ printf("%d ",tp.front()); tp.pop_front(); }
代码实现(以UVa10305为例)
伪代码:
Adapted from: https://class.coursera.org/algo-009/lecture/52
topo_sort()
- mark all nodes as unvisited
- for each vertex i∈G:
- if i not yet visited:
- dfs(i)
dfs(int u)
- mark u as currently being visited(-1)
- for each edge (u,v):
-if v is currently being visited:
- cyclic!
- if v not yet visited:
- dfs(v)
- put u at the front of the queue
- mark u as already visited
/* Source: UVa10305 Status: AC Done: 25/11/2015 Remarks: topological sort (dfs) */ #include <cstdio> #include <deque> #include <cstring> using namespace std; int G[105][105],visited[105],n,m; deque <int> tp; void dfs(int u){ int v; visited[u] = -1; //u is currently being visited for(v = 1; v <= n; v ++){ if(G[u][v] == 1){ if(visited[v] == -1)return; //cyclic if(visited[v] == 0) dfs(v); } } tp.push_front(u); visited[u]=1; // u is already visited return; } void topo_sort(){ int i; memset(visited,0,sizeof(visited)); //mark all nodes as unvisited for(i = 1; i <= n; i ++){ //for each vertex in G if(visited[i] == 0)dfs(i); //if(u is not yet visited) } } int main(){ int u,v,i,j; while(1){ scanf("%d %d",&n,&m); if(n == 0 && m == 0)break; for(i = 1; i <= m; i ++){ scanf("%d %d",&u,&v); G[u][v] = 1; } topo_sort(); while(!tp.empty()){ printf("%d ",tp.front()); tp.pop_front(); } printf("\n"); for(i=1;i<=n;i++) for(j=1;j<=n;j++) G[i][j]=0; } return 0; }
2. Kahn
不断地做这样的一件事,在余下的有向图中求出一个源,它是一个没有输入边的顶点,然后把它和所有从它出发的边都删除。如果有多个这样的源,可以任意选择一个。如果这样的源不存在,算法停止,因为该问题是无解的。
盲点:
1. current=1 (The importance of initialisation!)
2. 不断将新数加入队列从而实现循环
3. G[u][v]的判断(见注释)
/* Source: UVa10305 Status: AC Done: 25/11/2015 Remarks: topological sort (Kahn) */ #include <cstdio> #include <queue> #include <cstring> using namespace std; int G[105][105],deg[105],ans[105],n,m,current=1; queue <int> tp; void topo_sort(){ int i,j,v,u; for(i = 1; i <= n; i ++){ if(deg[i] == 0){ tp.push(i); } } while(!tp.empty()){ u=tp.front(); //u是当前的“根” tp.pop(); ans[current++]=u; deg[u] = -1; for(v = 1; v <= n; v ++){ if(G[u][v] == 1){ deg[v] --; } } for(v = 1; v <= n; v ++){ if(deg[v] == 0 && G[u][v]){ //G[u][v]判断v是否为u的子结点,避免把其他“根”重复加入队列 //样例数据: now u=1 tp:4 若无此判断 4会再次被加入队列 G[u][v] = 0; tp.push(v); } } } return; } int main(){ int u,v,i,j; while(1){ scanf("%d %d",&n,&m); if(n == 0 && m == 0)break; for(i = 1; i <= m; i ++){ scanf("%d %d",&u,&v); G[u][v] = 1; deg[v]++; } topo_sort(); for(i=1;i<=n;i++) printf("%d ",ans[i]); printf("\n"); memset(G,0,sizeof(G)); memset(deg,0,sizeof(deg)); memset(ans,0,sizeof(ans)); current = 1; } return 0; }