文章出處:極客時間《數據結構和算法之美》-作者:王爭。該系列文章是本人的學習筆記。
拓撲排序能解決的問題
在一個項目中會有很多源代碼文件。編譯器在編譯代碼的時候需要按照依賴關係,依次編譯每個源文件。例如A.java依賴B.java,那就需要先編譯B.java,再編譯A.java。要想完整編譯整個項目就需要確定一個全局的編譯順序。確定這樣一個全局的編譯順序就用到拓撲排序。
拓撲排序就是解決有向無環圖的圖中所有頂點的滿足依賴條件的頂點順序。
解決思路
可以將每個源文件看做一個頂點,源文件和源文件之間的依賴關係看做一條邊。圖的基本結構如下。
public class Graph {
private int v; // 頂點的個數
private LinkedList<Integer> adj[]; // 鄰接表
public Graph(int v) {
this.v = v;
adj = new LinkedList[v];
for (int i=0; i<v; ++i) {
adj[i] = new LinkedList<>();
}
}
public void addEdge(int s, int t) { // s先於t,邊s->t
adj[s].add(t);
}
}
排序算法有兩種方式BFS和DFS。
BFS遍歷
BFS遍歷,也稱爲Khan算法。在構建圖的時候如果A.java依賴B.java,那就從B到A有一條邊:B->A。那入度爲0的點就是最先編譯的。 找到入度爲0的頂點X,將其輸出到拓撲排序結果列表中,然後刪除以X爲起點的所有的邊。繼續查找入度爲0的頂點,添加到結果列表中。
public List<Integer> topSortByKahn(){
int[] inDegree = new int[v];
for(int i = 0; i< adjacency.length; i++){
for(Edge edge : adjacency[i]){
inDegree[edge.tid] ++;
}
}
Queue<Integer> queue = new LinkedList<>();
for(int i=0;i<inDegree.length;i++){
if(inDegree[i] == 0){
queue.add(i);
}
}
List<Integer> path = new ArrayList<>();
while(! queue.isEmpty()){
int node = queue.poll();
path.add(node);
for(Edge edge : adjacency[node]){
inDegree[edge.tid]--;
if(inDegree[edge.tid] == 0){
queue.offer(edge.tid);
}
}
}
return path;
}
DFS遍歷
按照深度優先搜索的方式,遍歷每個頂點。假如有條路徑是:A->B->C->E、A->D->C。
DFS的時候,如果先走的是第一條要先訪問了C、E纔會訪問D->C這條路線。這樣的話,就不能找到C什麼時候可以執行。所以需要將鄰接矩陣轉爲逆鄰接矩陣。
E->C->B->A、C->D->A。
說明A先執行了才能執行B,B、D先執行才能執行C,C執行了才能執行 E。這個順序符合要求。
在DFS處理環節,把一個頂點所依賴的所有節點先輸出,再輸出本節點。
public List<Integer> topSortByDFS(){
LinkedList<Integer>[] inverseAdg = new LinkedList[this.v];
for(int i = 0; i< adjacency.length; i++){
inverseAdg[i] = new LinkedList<>();
}
for(int i = 0; i< adjacency.length; i++){
for(Edge edge : adjacency[i]){
inverseAdg[edge.tid].add(edge.sid);
}
}
boolean[] visited = new boolean[v];
List<Integer> path = new ArrayList<>();
for(int i=0;i<this.v;i++){
if(visited[i] == false){
dfs(i,inverseAdg,visited,path);
}
}
return path;
}
private void dfs(int sid, LinkedList<Integer>[] inverseAdg, boolean[] visited,List<Integer> path) {
visited[sid] = true;
for(int tid : inverseAdg[sid]){
if(visited[tid] == false){
dfs(tid,inverseAdg,visited,path);
}
}
path.add(sid);
}