Fork me on GitHub

数据结构笔记:如何随机生成有向无环图


在验证有向无环图相关的各种算法时需要一些测试数据,手动构造的话太麻烦了,于是便想着能不能自动生成一些测试数据来。这个问题的难点在于如何保证生成的图没有环,查了一下相关资料,这个可以借助拓扑排序的原理来实现,想象一下一个有向无环图要对其拓扑排序,需要从图中找出一个入度为0的顶点,将它和它的出边都从图中删除,重复执行这个操作直到图为空,只需要逆向执行这个过程即可从拓扑排序的结果恢复出一个有向无环图,比如下面这个有向无环图:

对其拓扑排序的结果是:

a, b, c

那么只需要随机地将前面顶点连接到后面顶点即可将拓扑排序的结果还原为有向无环图,比如随机地从a连向b或c,从b连向c,不过需要注意的是因为拓扑排序会丢失边的信息,所以这个还原并不能保证和原图一致。


下面是针对这个原理写的一个工具类,输入拓扑排序,根据此拓扑排序生成随机有向无环图。

表示顶点和其邻接关系的实体类:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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的工具类:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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以渲染:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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文件,将上面的输出粘贴进去:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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

查看渲染效果:

image

通过图形渲染,能够更直观的看到,生成的确实是一个有向无环图,至此实现了测试数据可以自动生成,下面的实验可以开心的继续下去了。


.

posted @   CC11001100  阅读(4146)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2017-02-02 lintcode 66.67.68 二叉树遍历(前序、中序、后序)
2017-02-02 lintcode 443.岛屿的个数
2017-02-02 关于时间标记的思考
点击右上角即可分享
微信分享提示