在驗證有向無環圖相關的各種算法時需要一些測試數據,手動構造的話太麻煩了,於是便想着能不能自動生成一些測試數據來。這個問題的難點在於如何保證生成的圖沒有環,查了一下相關資料,這個可以借助拓撲排序的原理來實現,想象一下一個有向無環圖要對其拓撲排序,需要從圖中找出一個入度為0的頂點,將它和它的出邊都從圖中刪除,重復執行這個操作直到圖為空,只需要逆向執行這個過程即可從拓撲排序的結果恢復出一個有向無環圖,比如下面這個有向無環圖:

對其拓撲排序的結果是:
a, b, c
那么只需要隨機地將前面頂點連接到后面頂點即可將拓撲排序的結果還原為有向無環圖,比如隨機地從a連向b或c,從b連向c,不過需要注意的是因為拓撲排序會丟失邊的信息,所以這個還原並不能保證和原圖一致。
下面是針對這個原理寫的一個工具類,輸入拓撲排序,根據此拓撲排序生成隨機有向無環圖。
表示頂點和其鄰接關系的實體類:
package org.cc11001100.alg.graph.topologicalSorting.generateDAG;
import java.util.ArrayList;
import java.util.List;
/**
* @author CC11001100
*/
public class Vector<T> {
private T name;
private List<Vector<T>> from;
private List<Vector<T>> to;
public Vector(T name) {
this.name = name;
this.from = new ArrayList<>();
this.to = new ArrayList<>();
}
public T getName() {
return name;
}
public void setName(T name) {
this.name = name;
}
public List<Vector<T>> getFrom() {
return from;
}
public List<Vector<T>> getTo() {
return to;
}
}
然后是根據拓撲排序生成DAG的工具類:
package org.cc11001100.alg.graph.topologicalSorting.generateDAG;
import java.util.List;
import java.util.Random;
/**
* 生成隨機DAG
*
* @author CC11001100
*/
public class DAGGenerator {
/**
* 傳入一個拓撲排序好的頂點列表,然后從這個拓撲排序中隨機生成一個DAG
*
* @param topologicalSortedVectorList
* @return
*/
public static <T> void random(List<Vector<T>> topologicalSortedVectorList) {
for (int i = 0, end = topologicalSortedVectorList.size(); i < end; i++) {
for (int j = i + 1; j < end; j++) {
if (Math.random() < 0.5) {
Vector<T> from = topologicalSortedVectorList.get(i);
Vector<T> to = topologicalSortedVectorList.get(j);
from.getTo().add(to);
to.getFrom().add(from);
}
}
}
// 檢查是否有除了第一個頂點之外入度為0的頂點,如果有的話就從前面的頂點中隨機選一個連過來,這個是為了避免有獨立的頂點存在
Random random = new Random();
for (int i = 1, end = topologicalSortedVectorList.size(); i < end; i++) {
Vector<T> to = topologicalSortedVectorList.get(i);
if (to.getFrom().isEmpty()) {
Vector<T> from = topologicalSortedVectorList.get(random.nextInt(i));
from.getTo().add(to);
to.getFrom().add(from);
}
}
}
}
為了更直觀的觀察生成的結果,我們將其繪制為圖形,這里使用dot language,IDEA可以借助PlatUML插件方便的渲染,這里不是介紹工具或語言的用法的,如有興趣請自行查閱相關資料。
測試類,將生成結果轉換為dot language以渲染:
package org.cc11001100.alg.graph.topologicalSorting.generateDAG;
import java.util.ArrayList;
import java.util.List;
/**
* @author CC11001100
*/
public class TestDAGGenerator {
/**
* FORMAT:
*
* <pre>
* digraph abc{
* a;
* b;
* c;
* d;
*
* a -> b;
* b -> d;
* c -> d;
* }
* </pre>
*
* @param graphName
* @param vectorList
*/
public static <T> String convertToDot(String graphName, List<Vector<T>> vectorList) {
StringBuilder sb = new StringBuilder();
sb.append("@startuml\n\n")
.append("digraph ").append(graphName).append(" { \n");
vectorList.forEach(vector -> sb.append(" ").append(vector.getName()).append(";\n"));
sb.append("\n");
vectorList.forEach(from -> from.getTo().forEach(to -> {
sb.append(" ").append(from.getName()).append(" -> ").append(to.getName()).append(";\n");
}));
sb.append("}\n")
.append("\n@enduml\n");
return sb.toString();
}
public static void main(String[] args) {
final int vectorCount = 10;
List<Vector<Integer>> vectorList = new ArrayList<>(vectorCount);
for (int i = 0; i < vectorCount; i++) {
vectorList.add(new Vector<>(i));
}
DAGGenerator.random(vectorList);
String dotGraph = convertToDot("test_DAG_generator", vectorList);
System.out.println(dotGraph);
}
}
新建.puml文件,將上面的輸出粘貼進去:
@startuml
digraph test_DAG_generator {
0;
1;
2;
3;
4;
5;
6;
7;
8;
9;
0 -> 3;
0 -> 6;
0 -> 9;
0 -> 1;
1 -> 2;
1 -> 4;
1 -> 9;
2 -> 4;
2 -> 8;
2 -> 9;
3 -> 4;
3 -> 5;
3 -> 6;
4 -> 5;
4 -> 6;
4 -> 7;
5 -> 7;
5 -> 8;
6 -> 7;
6 -> 8;
7 -> 9;
}
@enduml
查看渲染效果:

通過圖形渲染,能夠更直觀的看到,生成的確實是一個有向無環圖,至此實現了測試數據可以自動生成,下面的實驗可以開心的繼續下去了。
.
