数据结构笔记:如何随机生成有向无环图
在验证有向无环图相关的各种算法时需要一些测试数据,手动构造的话太麻烦了,于是便想着能不能自动生成一些测试数据来。这个问题的难点在于如何保证生成的图没有环,查了一下相关资料,这个可以借助拓扑排序的原理来实现,想象一下一个有向无环图要对其拓扑排序,需要从图中找出一个入度为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
查看渲染效果:
通过图形渲染,能够更直观的看到,生成的确实是一个有向无环图,至此实现了测试数据可以自动生成,下面的实验可以开心的继续下去了。
.