最近又寫到有向圖的拓撲排序,這段排序代碼在幾年前也寫過。做個簡單記錄,本次用到了google guava的Graph類。
主要是用方法可以參考:http://www.lxweimin.com/p/78786a4f2cf1
相關排序可以參考: https://juejin.cn/post/7251844608774209592
圖1
拓撲排序:
Visited node: A
Visited node: D
Visited node: E
Visited node: B
Visited node: C
Visited node: F
但在實際的工作中,并不是單純的對節點進行排序或者遍歷,比如說常用的就是A有三條下游節點時,則選擇其中一條路徑執行,其余路徑均不可執行。
圖2
在拓撲排序的基礎上,我們增加不執行節點的判斷與處理。具體處理邏輯如下:
public class UnReachableTopologyTraversal {
public static void topologyTraverse(MutableGraph<String> graph, Function<String, Boolean> function) {
// 1.1 拓撲排序需要的輔助隊列。
Queue<String> queue = Queues.newLinkedBlockingQueue();
//1.2 初始化本次不執行節點集合
List<String> unReachNodeList = Lists.newArrayList();
// 2. 將root節點(入度為0的節點)入隊列。
for (String node : graph.nodes()) {
if (graph.inDegree(node) == 0) {
queue.add(node);
}
}
// 3. 非root節點(入度非0的節點)對應的入度記錄表。
Map<String, Integer> nodeIdInDegreeMap = graph.nodes().stream()
.filter(task -> graph.inDegree(task) != 0)
.collect(Collectors.toMap(node -> node, node -> graph.inDegree(node)));
// 4. 拓撲遍歷
while (!queue.isEmpty()) {
//5. 隨機選取其中一個可執行節點
List<String> allNodes = new ArrayList<>(queue);
Collections.shuffle(allNodes);
queue = new LinkedList<>(allNodes);
String current = queue.poll();
// 6. 判斷是不是不可執行節點,如果是不可執行節點,則跳過
if (unReachNodeList.contains(current)) {
continue;
}
//7.1 執行本次節點函數
function.apply(current);
//7.2 后續節點入度減一
subSuccessorInDegree(current, graph, nodeIdInDegreeMap);
//8.1 標記不可執行節點
for (String node : allNodes) {
if (!unReachNodeList.contains(node) && !StringUtils.equals(node,
current)) {
unReachNodeList.add(node);
subSuccessorInDegree(node, graph, nodeIdInDegreeMap);
markSuccessorUnReachable(graph, node, unReachNodeList, nodeIdInDegreeMap);
}
}
//9. 拓撲排序, 將當前節點的后繼節點入度減一, 如果入度為0, 則加入隊列, 等待調度。
for (String node : graph.successors(current)) {
if (nodeIdInDegreeMap.get(node) == 0) {
queue.add(node);
}
}
}
}
/**
* 將當前節點的后續節點入度-1
* @param currentNode
* @param graph
* @param nodeIdInDegreeMap
*/
private static void subSuccessorInDegree(String currentNode,
Graph<String> graph, Map<String, Integer> nodeIdInDegreeMap) {
Set<String> successorNodes = graph.successors(currentNode);
if (successorNodes == null || successorNodes.size() == 0) {
return;
}
for (String successor : successorNodes) {
Integer inDegree = nodeIdInDegreeMap.get(successor);
nodeIdInDegreeMap.put(successor, inDegree - 1);
}
}
/**
* 標記當前節點的后續節點為不可執行節點
* @param graph
* @param currentNode
* @param unReachNodeList
* @param nodeIdInDegreeMap
*/
private static void markSuccessorUnReachable(Graph<String> graph, String currentNode,
List<String> unReachNodeList, Map<String, Integer> nodeIdInDegreeMap) {
Set<String> afterNodeList = graph.successors(currentNode);
for (String afterNode : afterNodeList) {
boolean unReachable = true;
//當前節點的前置節點如果存在不可執行節點,則不需要遞歸不可執行
Set<String> preAfterNodeList = graph.predecessors(afterNode);
for (String preAfterNode : preAfterNodeList) {
if (!unReachNodeList.contains(preAfterNode)) {
unReachable = false;
}
}
//后續節點為不可執行節點,繼續遞歸不可執行
if (unReachable) {
unReachNodeList.add(afterNode);
subSuccessorInDegree(afterNode, graph, nodeIdInDegreeMap);
markSuccessorUnReachable(graph, afterNode, unReachNodeList, nodeIdInDegreeMap);
}
}
}
public static void main(String[] args) {
MutableGraph<String> graph = GraphBuilder.directed() //指定為有向圖
.nodeOrder(ElementOrder.<String>insertion()) //節點按插入順序輸出
//(還可以取值無序unordered()、節點類型的自然順序natural())
.expectedNodeCount(6) //預期節點數
.allowsSelfLoops(false) //不允許自環
.build();
graph.putEdge("A", "B");
graph.putEdge("A", "D");
graph.putEdge("A", "E");
graph.putEdge("B", "C");
graph.putEdge("C", "F");
graph.putEdge("D", "F");
graph.addNode("A");
graph.addNode("B");
graph.addNode("C");
graph.addNode("D");
graph.addNode("E");
System.out.println("選擇分支執行:");
UnReachableTopologyTraversal.topologyTraverse(graph, node -> {
System.out.println("Visited node: " + node);
});
}
}
結果1
結果2
結果3