有向无环图的拓扑排序
-
有向无环图 (DAG) 的拓扑排序是顶点的线性排序,使得对于每个有向边 u, v,顶点 u排在 v 之前。如果图不是 DAG,则无法对图进行拓扑排序。
-
例如,下图的拓扑排序是「5 4 2 3 1 0」。一个图可以有多个拓扑排序。例如,下图的另一种拓扑排序是「4 5 2 3 1 0」。拓扑排序中的第一个顶点始终是入度为 0 的顶点(没有传入边的顶点)。
拓扑排序与深度优先遍历(DFS):
- 在DFS,我们打印一个顶点,然后对其相邻顶点递归调用 DFS。
- 在拓扑排序中,我们需要在相邻顶点之前打印一个顶点。例如,在给定的图中,顶点 ‘5’ 应该在顶点 ‘0’ 之前打印,但与DFS不同的是,顶点 ‘4’ 也应该在顶点 ‘0’ 之前打印。所以拓扑排序不同于DFS。例如,所示图的 DFS 是「5 2 3 1 0 4」,但它不是拓扑排序。
Algorithm:
- 在DFS,我们从一个顶点开始,首先打印它,然后对其相邻顶点递归调用 DFS。
- 在拓扑排序中,我们使用临时堆栈。我们不会立即打印顶点,我们首先对其所有相邻顶点递归调用拓扑排序,然后将其压入堆栈。最后,打印堆栈的内容。请注意,仅当顶点的所有相邻顶点(及其相邻顶点等)都已在堆栈中时,才会将顶点压入堆栈。
下面是是上述方法的实现过程:
Code:
static class Graph {
private int V;
private List<List<Integer>> adj;
//初始化构建图
public Graph(int V) {
this.V = V;
adj = new ArrayList<>();
for (int i = 0; i < this.V; i++) {
adj.add(new ArrayList<>());
}
}
//有向图
public void addEdge(int u, int v) {
adj.get(u).add(v);
}
public void topologicalSortUtil(int u, boolean[] vis, Stack<Integer> stk) {
vis[u] = true;//标记当前节点被访问过
for (int v : adj.get(u)) {
if (!vis[v]) topologicalSortUtil(v, vis, stk);
}
stk.push(u);//加入当前节点
}
public void topologicalSort() {
Stack<Integer> stk = new Stack<>();
boolean[] vis = new boolean[V];
for (int u = 0; u < V; u++) {//依次遍历
if (!vis[u]) topologicalSortUtil(u, vis, stk);
}
while (!stk.isEmpty()) {
System.out.print(stk.pop() + " ");
}
}
public static void main(String args[]) {
// Create a graph given in the above diagram
Graph g = new Graph(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);
System.out.println("Following is a Topological sort of the given graph");
g.topologicalSort();
//5 4 2 3 1 0
}
}
复杂度分析:
- 时间复杂度: O(V+E)。
上面的算法只是带有额外堆栈的 DFS。所以时间复杂度与 DFS 相同。 - 空间复杂度: O(V)。
堆栈需要额外的空间。