1. 程式人生 > >拓撲排序(Topologicalsort)之Java實現

拓撲排序(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即可。參考連結:維基百科