拓撲排序(Topologicalsort)之Java實現
拓撲排序演算法介紹
拓撲排序解決的是一系列相互依賴的事件的排序問題,比如Ant中有很多的Task,而某些Task依賴於另外的Task,編譯之前需要清理空間,打包之前要先編譯,但其它一些Task處理順序可以調換(是無所謂前後,不是並行), 如何安排Task的執行順序就可以用拓撲排序解決。熟悉Java的朋友應該都知道Spring,一個非常優秀的解決元件(Bean)依賴的框架,元件之間可能有依賴關係,也可能沒有關係,如何按順序建立元件也是相同的問題。本文使用的是圖搜尋演算法裡面的深度優先排序演算法解決問題。需要特別指出的是拓撲排序演算法的結果很可能有多個(不依賴的可以調換位置),而演算法也可以有多種,深度優先排序演算法只是其中一種而已。拓撲排序為線性排序,效率為O(|V|+|E|),其中|V|表示頂點數,|E|表示邊的數量。
拓撲排序演算法Java實現
影象(Graph)的Java抽象實現
圖可以抽象為頂點和邊,分為有向圖和無向圖,拓撲排序裡面使用的事有向圖(依賴),本文中圖的邊用相鄰連結串列方法表示。每一個頂點有名字(name),顏色(color, 搜尋時候用來標記處理狀態),父節點(parent,搜尋結束可以得到多棵樹),開始處理時間(discover),結束處理時間(finish)。請注意Vertex類override了equals和hash方法。具體程式碼如下:
enum Color { WHITE, GRAY, BLACK } static class Vertex { private String name; private Color color; private Vertex parent; private int discover; private int finish; public Vertex(String name) { this.name = name; this.color = Color.WHITE; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } public Vertex getParent() { return parent; } public void setParent(Vertex parent) { this.parent = parent; } public int getDiscover() { return discover; } public void setDiscover(int discover) { this.discover = discover; } public int getFinish() { return finish; } public void setFinish(int finish) { this.finish = finish; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Vertex other = (Vertex) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } } static class Graph { private Set<Vertex> vertexSet = new HashSet<Vertex>(); // 相鄰的節點 private Map<Vertex, Vertex[]> adjacencys = new HashMap<Vertex, Vertex[]>(); public Set<Vertex> getVertexSet() { return vertexSet; } public void setVertexSet(Set<Vertex> vertexSet) { this.vertexSet = vertexSet; } public Map<Vertex, Vertex[]> getAdjacencys() { return adjacencys; } public void setAdjacencys(Map<Vertex, Vertex[]> adjacencys) { this.adjacencys = adjacencys; } }
深度優先演算法
遍歷圖的頂點,如果當前頂點還沒有處理過(color為white),就處理該節點(visitVertex),處理節點的演算法(visitVertex)也不難,時間維度加1,當前節點顏色置為gray(開始處理),然後優先處理其子節點(深度優先),結束之後時間維度加1,當前節點顏色置為black(結束處理)。此時就可以把該節點放到目標連結串列裡面了(最終排好序的連結串列)。由於Java裡面Integer值不可變(Immutable),只能選擇全域性變數或者自己實現時間計數器,本文選擇後者(TimeRecorder)。程式碼如下:
static class TimeRecorder { private int time = 0; public int getTime() { return time; } public void setTime(int time) { this.time = time; } } public static Vertex[] topologicalSort(Graph graph) { Set<Vertex> vertexSet = graph.getVertexSet(); if (vertexSet.size() < 2) { return vertexSet.toArray(new Vertex[0]); } LinkedList<Vertex> sortedList = new LinkedList<Vertex>(); TimeRecorder recorder = new TimeRecorder(); for (Vertex vertex : vertexSet) { if (vertex.color == Color.WHITE) { visitVertex(graph, vertex, recorder, sortedList); } } return sortedList.toArray(new Vertex[0]); } /** * 深度優先搜尋(Depth First Search) */ public static void visitVertex(Graph graph, Vertex vertex, TimeRecorder recorder, LinkedList<Vertex> sortedList) { recorder.time += 1; vertex.color = Color.GRAY; vertex.discover = recorder.time; Map<Vertex, Vertex[]> edgeMap = graph.getAdjacencys(); Vertex[] adjacencys = edgeMap.get(vertex); if (adjacencys != null && adjacencys.length > 0) { for (Vertex adjacency : adjacencys) { if (adjacency.color == Color.WHITE) { adjacency.parent = vertex; visitVertex(graph, adjacency, recorder, sortedList); } } } recorder.time += 1; vertex.color = Color.BLACK; vertex.finish = recorder.time; sortedList.addLast(vertex); }
構建圖以及測試
我們的測試圖例如下(箭頭的方向表示的是依賴):
為了列印排好序的結果,實現了printVertex函式。測試程式碼如下:
public static void main(String[] args) {
Graph graph = new Graph();
Set<Vertex> vertexSet = graph.getVertexSet();
Map<Vertex, Vertex[]> edgeMap = graph.getAdjacencys();
Vertex twoVertex = new Vertex("2");
Vertex threeVertex = new Vertex("3");
Vertex fiveVertex = new Vertex("5");
Vertex sevenVertex = new Vertex("7");
Vertex eightVertex = new Vertex("8");
Vertex nineVertex = new Vertex("9");
Vertex tenVertex = new Vertex("10");
Vertex elevenVertex = new Vertex("11");
vertexSet.add(twoVertex);
vertexSet.add(threeVertex);
vertexSet.add(fiveVertex);
vertexSet.add(sevenVertex);
vertexSet.add(eightVertex);
vertexSet.add(nineVertex);
vertexSet.add(tenVertex);
vertexSet.add(elevenVertex);
edgeMap.put(twoVertex, new Vertex[] { elevenVertex });
edgeMap.put(nineVertex, new Vertex[] { elevenVertex, eightVertex });
edgeMap.put(tenVertex, new Vertex[] { elevenVertex, threeVertex });
edgeMap.put(elevenVertex, new Vertex[] { sevenVertex, fiveVertex });
edgeMap.put(eightVertex, new Vertex[] { sevenVertex, threeVertex });
Vertex[] sortedVertexs = topologicalSort(graph);
printVertex(sortedVertexs);
}
public static void printVertex(Vertex[] Vertexs) {
for (Vertex vertex : Vertexs) {
System.out.println(vertex.getName() + " discover time:"
+ vertex.getDiscover() + " finish time:"
+ vertex.getFinish());
}
}
後記
以上Java實現參考的是演算法導論的深度優先排序演算法。如果想對排序的精確度有更好的控制,可以在Vertex類中加一個priority屬性。每一次遍歷之前都針對頂點以priority即可。參考連結:維基百科