考慮你有修課的順序如下, 箭頭說明該課程有相依性. 如 計概->計組 指 "計概" 要先修完後才能修 "計組":
而我們的問題是要找出一種修課的順序可以滿足上面拓撲圖的要求, 而這類的問題稱之為 Topology sort. 底下是 wiki 上面較詳細的說明:
上面說明的 vertex 就可以看成課程, 而 edge 說明課程間的相依性. 而這樣的 Graph 必須滿足 DAG (directed acyclic grapn) 而這類問題的圖形也稱為 AOV (activity on vertex).
Using DFS as Solution:
要解決這個問題一個常見的解法是使用 DFS (Deep First Search). 在跑 DFS 的過程中每個 vertex 都會產生所謂的 discover time 與 finish time. 而 finish time 較小的 vertex 相對 finish time 較大的 vertex 在圖形中較 deep 的位置. 以下圖來說, Vertex 7 的 finish time 會比 Vertex 0 小:
底下是 DFS 的 pseudo code:
上面的 u.f 指的即是 finish time. 因此直覺的可以將 vertex 依其 finish time 以 decreasing order 排列後, 取出的的順序便是其中一個可能的解法. (finish time 較大的先取出來, 即由 root 依序往其 leaf 移動). 而其實 finish time 大的先取; 小的後取 的概念類似於 ADT Stack 的想法 (FILO - First in Last out 或是 LIFO - Last in First out), 因此下面的實作中當每個 vertex 狀態由 Gray->Black (finish), 便將之加入 stack, 最後將該 stack 返回並依序取出 vertex 便是 Topology sort 的結果.
Sample Code
底下範例代碼使用 JGraphT 套件並使用上面說明的演算法實作 Topological Sort. 主要透過實作一個課製的 TraversalListenerAdapter 來監聽 Vertex finished 的時候, 並將之加入到 Stack 中, 方便後續使用 finished time 以 descending 的順序取出對應的 Vertex:
- package lab
- import static org.junit.Assert.*
- import org.jgraph.graph.DefaultEdge
- import org.jgrapht.DirectedGraph
- import org.jgrapht.event.ConnectedComponentTraversalEvent
- import org.jgrapht.event.EdgeTraversalEvent
- import org.jgrapht.event.TraversalListenerAdapter
- import org.jgrapht.event.VertexTraversalEvent
- import org.jgrapht.graph.DefaultDirectedGraph
- import org.jgrapht.traverse.DepthFirstIterator
- import org.jgrapht.traverse.GraphIterator
- class MyListener extends TraversalListenerAdapter
- {
- Stack
stack = new Stack () - @Override
- public void vertexFinished(VertexTraversalEvent
e) { - printf("V=%s finished.\n", e.getVertex())
- stack.push(e.getVertex())
- }
- }
- // 0) Initialize Graph
- DirectedGraph
g = - new DefaultDirectedGraph
(DefaultEdge. class) - def vertex = ["微積分上", "微積分下", "機率", "計概", "計組",
- "作業系統", "計程", "演算法上", "演算法下", "計算機網路"]
- vertex.each{
- g.addVertex(it)
- }
- g.addEdge("微積分上", "微積分下")
- g.addEdge("微積分下","機率")
- g.addEdge("機率", "計算機網路")
- g.addEdge("計概", "計組"); g.addEdge("計組", "作業系統"); g.addEdge("作業系統", "計算機網路")
- g.addEdge("計程", "演算法上"); g.addEdge("演算法上", "演算法下"); g.addEdge("演算法下", "作業系統")
- // 1) Start DFS and store finished vertex into stack in order
- GraphIterator
iterator = new DepthFirstIterator (g) - MyListener listener = new MyListener()
- iterator.addTraversalListener(listener)
- while(iterator.hasNext()) iterator.next()
- // 2) Pop out the stack with finished vertex
- while(!listener.stack.isEmpty()) printf("%s ", listener.stack.pop())
- println()
Supplement
* Coursera - Topological Sort
* [ Alg info ] Topology sort using DFS
* JGraphT - DFS
* Topological Ordering