精华内容
下载资源
问答
  • 从零开始自己的DAG作业依赖(一)--前期准备
    千次阅读
    2022-04-01 11:27:48

    背景:

    由于业务场景需要展示作业之间的依赖关系,由于一些开源的插件和当前的业务逻辑有一些冲突,个人打算尝试从零开始,一点点实现自己的DAG图。同时用博客记录自己实现过程和总结,不正确的地方,欢迎指正提升。

    场景分析:

    1. 数据开发中常常有作业之间的依赖,在执行作业的时候,可能会出现有些作业失败,或者重试,这时候,运维或者开发的人,需要有一个很直观的图去展示作业之间的依赖,这时候DAG 依赖流程图就是一个很好的展现形式。
    2. 大数据场景下,可能会出现很多节点,甚至可能达到上千,甚至上万的节点,节点之间的边有可能也会很多,大的数据下,图的渲染时间需要考虑在内
    3. 用户有可能在看了图之后,想直接重跑或者查看某个节点的上下游节点,等等运维操作
    4. 用户可能需要图拖动某个算子,点击某个算子,复制某些算子的名称等一些操作

    实现目标:

    基于以上客户的要求,我们大致明确要实现目标和需求,梳理如下

    1. 用图的方式展示作业之间的依赖,基本能力要达到
    2. 图要能够支撑大数据场景,在很多节点下
      • 节点之间布局清晰
      • 节点的之间的连线不要重叠,少交叉
      • 图能够支持缩放,拖动
      • 图中节点支持拖动
    3. 图要支持运维能力
      • 悬浮某个节点 ,展示该节点的上下游,能够区分上下游节点
      • 选中某个节点,以及支持选中某个节点全部上游或者下游节点
      • 右键可以定制化业务操作,例如作业实例的重跑,停止
      • 作业名称支持复制
      • 选中具体某个节点可以展示该节点的信息面板
    4. 优化提升
      • 提供鸟瞰图的功能,保证大数据下可以查找节点
      • 提供搜索功能,能够精准在图上定位出要找的作业

    交互设计稿:

    基于以上目标,设计稿大致如下

    技术选型:

    一般的DAG,就两种方式,一种是canvas实现,另外一种就是svg,由于svg 对于事件处理比较简单,但是性能会差一些,后续如果svg 性能搞不定,可以使用canvas优化,新手,所以这里我选择了svg。用了svg.js,这个开源的插件,封装了一些简单的svg 函数,可以减少部分工作量。

    实现过程概览:

    个人打算分一下几个方面,逐步实现。

    1. 规划,背景,交互设计方案,技术选型
    2. 节点的分层布局算法
    3. 节点之间的线条路径
    4. 图上运维--右键,选中,节点复制等
    5. 图上事件--缩放,平移,节点拖动等
    6. 后期优化--纵向排列优化
    7. 后期优化--鸟瞰图以及搜索框及其他
    8. 后期优化--大数据节点优化

    基本工作已经准备完了,接下来开搞!

    本文由华为云发布。

    更多相关内容
  • 一个基于React的运维/监控DAG图 |简体中文 :sparkles:特性 支持左到右,上到下的布局 支持自定义状态,左上角自定义状态注释 支持自定义基线样式,以及将鼠标悬停在焦点上 支持线段标签样式 支持摘要,锚点,线段...
  • 具有边缘标记的简单DAG(有向无环)模块。 安装 $ npm install dagjs 用法 let Dag = require ( 'dagjs' ) ; let dag = new Dag ( ) ; // ... 例子 添加边: let dag = new Dag ( ) ; // add(from, to, tags, ...
  • 《编译原理》 DAG 与求优化后的 4 元式代码 - 例题解析DAG (Directed Acyylic Graph) 无环路有向(一)基本块基本块是指程序中一顺序执行的语句序列, 其中只有一个入口语句 (第一个语句) 和一个出口语句(最后...

    《编译原理》画 DAG 图与求优化后的 4 元式代码 - 例题解析

    DAG 图 (Directed Acyylic Graph) 无环路有向图

    (一)基本块

    基本块是指程序中一顺序执行的语句序列, 其中只有一个入口语句 (第一个语句) 和一个出口语句(最后一个语句)

    对于一个基本块来说, 执行时只能从其入口语句进入, 从其出口语句退出语句出口语句任何控制转移四元式

    入口语句所转向的目标语句

    (二)划分基本块的步骤

    1, 求四元式序列中各个基本块的入口语句.

    1 程序的第一个语句

    2 能由条件或无条件转移语句转移到的语句

    3 紧跟在条件转移语句后面的语句

    2, 对每一入口语句, 构造所属的基本块, 该基本块由:

    1)该入口语句到下一入口语句 (不包括下一入口语句) 之间的语句序列组成

    2)该入口语句到一转移语句 (包括该转移语句) 之间的语句序列组成

    3)该入口语句到一停语句 (包括该停语句) 之间的语句序列组成

    3, 凡是未包含在某一基本块中的语句, 都是程序中控制流程不可达的语句, 可删除它们.

    例题:

    对于下面给出的求最大公因子的程序, 可以根据基本块的构造规则与其划分基本块

    ab7653affab982b574eb7acc55df2e04.gif

    基本块构造步骤:

    (1): 由规则 (1) 中的 1 可知语句 (1) 是一个入口语句

    (2): 由规则 (1) 中的 2 可知, 语句 (3) 和 (8) 均是人口语句

    (3): 由规则 (1) 中的 3 可知, 语句 (5) 是二个人口语句, 可以用 "+" 在人口语句的左侧作标记.

    (4): 由规则 (2) 可以划分该程序为四个基本块, 它们分别是:

    语句 (1),(2) 组成的基本块 B1

    语句 (3),(4) 组成的基本块 B2

    语句 (5),(6) 和 (7) 组成的基本块 B3

    语句 (8) .(9) 组成的基本块 B4

    程序中在代码段左侧对各个基本块进行了标记.

    (三)程序控制流程流图

    定义: 以基本块为结点, 控制程序流向作为有向边, 画出的有向图称为流图.

    特点:

    具有唯一首结点的有向图

    从首结点开始到流图中任何结点都有通路

    如果一个结点的基本块的入口语句是程序的第一条语句, 则称此结点为首结点

    程序控制流程流图的表示

    一个控制流程图可表示成一个三元组:

    G=(N,E,n0 )

    N: 所有结点 (基本块) 集;

    E: 所有有向边集;

    n0 : 首结点.

    有向边:

    当下述条件有一个成立时, 从结点 i 有一有向边引向结点 j:

    1 基本块 j 在程序的位置紧跟在 i 后, 且 i 的出口语句不是无条件转移或停语句

    2 i 的出口是 goto(S) 或 if goto(S), 而 (S) 是 j 的入口语句

    ab7653affab982b574eb7acc55df2e04.gif

    构造程序控制流图

    对程序基本块:

    ab7653affab982b574eb7acc55df2e04.gif

    构造以下程序控制流图:

    ab7653affab982b574eb7acc55df2e04.gif

    (四)基本块的 DAG 表示

    DAG Directed Acyclic Graph 无环路有向图

    定义:

    (1) 在一个有向图中, 若结点 ni 有弧指向结点 nj, 则 ni 是 nj 的父结点, nj 是 ni 的子结点;

    (2) 若 n1,n2,...,nk 间存在有向弧 n1→n2→...→nk, 则称 n1 到 nk 之间存在一条通路, 若有 nk=n1, 则称该通路为环路;

    (3) 若有向图中任意通路都不是环路, 则称该图为无环路有向图(DAG)

    用来描述基本块的 DAG:

    (1) 图的叶结点以一标识符或常数做标记, 表示该结点代表该变量或常数的值.

    (2) 图的内部结点以一运算符作为标记;

    (3) 图中各个结点上可能附加一个或多个标识符, 表示这些标识符具有该结点所代表的值, 简称附标.

    四元式对应的 DAG 结点形式

    按其四元式对应结点的后继个数分成四种类型: 0 型, 1 型, 2 型, 3 型

    ab7653affab982b574eb7acc55df2e04.gif

    (五)DAG 图构造例题

    对于基本块 P(1)S0:=2

    (2)S4:=2

    (3)S1:=1.5

    (4)S2:=T-C

    (5)S3:=T+C

    (6)S5:=S3

    (7)R:=2/S3

    (8)S6:=R

    (9)H:=R*S2

    (1)试用 DAG 进行优化并重写基本块

    (2)假定只有 R,H 在基本块出口是活跃的, 试写出优化后的 4 元式序列

    (只需要还原活跃变量)

    解析:

    (1)画出 DAG 图如下:

    画图的步骤就是: 根据基本块, 一部一部组装

    ab7653affab982b574eb7acc55df2e04.gif

    (2)假定只有 R,H 在基本块出口是活跃的, 试写出优化后的 4 元式序列

    (只需要还原活跃变量)

    优化后的 4 元式代码可以写为:(1)S2:=T-C

    (2)S3:=T+C

    (3)R:=2/S3

    (4)H:=R*S2

    解释:

    与原来的基本块相比较可以看出:

    原基本块中的 (2) 和 (7) 中的已知量都已经合并. 因为 (2) 中 S4 := 2,(7) 中用 2, 所以合并.

    (5) 和 (8) 中的公共子表达式 T+C 只在 (5) 中计算一次, 在 (8) 中 直接引用其结果, 所以删除了多余运算.

    (6) 中的无用赋值已被删除. S5 := S3,S5 后面没有再用, 所以就和 S3 一起表示.

    除了可以应用 DAG 进行上述的优化外, 还可以从基本块的 DAG 中得到一些其他信息:

    DAG 叶结点上标记的标识符是在该基本块之前的基本块内被定值, 并在该基本块内被引用的标识符.

    DAG 各结点上的附加标识符是在基本块内被定值, 并可以在基本块后被引用的标识符.

    如果确认某结点的一个附加标记在基本块后不会被引用, 则该标识符的定值语句可以作为死代码被删除.

    假设上面例子中 S0~S6. 在基本块后面都不会被引用只有 R, H 在基本块出口是活跃的则优化后的四元式序列可以写为:(1)S2:=T-C

    (2)S3:=T+C

    (3)R:=2/S3

    (4)H:=R*S2

    来源: https://www.cnblogs.com/xpwi/p/11073220.html

    展开全文
  • 从上可以看出,直线实现起来是比较简单的,但是节点很少的情况,还是可以勉强可以接受的,如果节点太多,线条复杂,基本没办法看了,效果不是很好。 曲线方式 曲线是比较常见的方式,这里可以用3

    概述

    分层布局弄好之后,每一层的节点上下的相对位置基本确定了,我们最简单的方式,就是每一层平铺所有节点,节点宽度固定以及间距固定,每个节点的位置基本就确定了。我们只要基于这些节点进行连线就可以了。

    方案思路

    直线方式

    最简单的方式就是直线,我们只要确定两个节点之间的相对具体坐标,然后两点之间画一条直线就可以了。效果如下

    从上图可以看出,直线实现起来是比较简单的,但是节点很少的情况,还是可以勉强可以接受的,如果节点太多,线条复杂,基本没办法看了,效果不是很好。

    曲线方式

    曲线是比较常见的方式,这里可以用3次贝塞尔曲线或者2次贝塞尔曲线,算好相应的控制点即可。但是这些控制点不是很好算,并且通用的一些控制点,在一些复杂情况下,展示的效果也不是很好,这里也没打算使用这个。

    折线方式

    就是通过折线,曼哈顿的方式,在空白的地方进行拐点,进行连线,这种方式可以很好利用空间,并且可以规避节点,不会出现 线和节点的相互遮盖,这里我就采用了这种方式。由于我们的业务场景,节点比较多,并且层级不是很多,这里我投机采用了一种比较简单的方式来画这个折线。首先看下效果

    在上述情况,我们考虑最简单的场景,就是最多用4个点来画这条折线,这种画法应该是最简单的。因为图是从上往下画的,我们只要考虑 中间节点Y坐标即可。但是要考虑尽可能的不要让线条重叠。 我们可以把每层节点按照x坐标排序,然后划线的时候,按照x轴,从左到右的依次划线,并且保证每条线没有重叠的部分。

    具体实现

    节点排序

    function divideNodesIntoLayer(nodeList){
                // 清空缓存的线段结合
                nodeLines = {};
                var lineNodes = {};
                for(var layer = 1; layer <= maxGraphLayer; layer++){
                    lineNodes[layer] = [];
                    for(var j = 0; j < nodeList.length; j++){
                        if(nodeList[j].layer === layer){
                            lineNodes[layer].push(nodeList[j]);
                        }
                    }
                    lineNodes[layer].sort(function(a, b){
                        return a.x - b.x;
                    })
                }
                return lineNodes;
    }

    从距离下层节点40px的地方,往上遍历,每次增加一个步长,判断是否用重叠,直到找到一个不重叠的Y值为止。

    function calcMidY(){
        var midY = endY - 40;
        while (true) {
            var flag = false;
            if (nodeLines[layer]) {
                for (var i = 0; i < nodeLines[layer].length; i++) {
                    var line = nodeLines[layer][i];
                    if (line.startY === midY) {
                        //  判断是否重叠
                        if (checkCross(startX, endX, line.startX, line.endX)) {
                            flag = true;
                        }
                    }
                    if (flag) break;
                }
            } else {
                nodeLines[layer] = [];
            }
            if (!flag) break;
            midY -= lineDis;
        }
        if (startX !== endX) {
                    //  缓存已经画的线段
            nodeLines[layer].push({
                startX: startX,
                startY: midY,
                endX: endX,
                endY: midY
            })
        }
    
    }

    找到了拐点的Y值,整条折线的坐标都清晰了,根据4个点划线即可。

    总结

    以上是一种很简单的实现方式,遇到复杂场景,还是会有线条的重叠,更准确的做法是要规避所有障碍,寻找路径,这种复杂的做法,后续会进一步尝试。

    本文由华为云发布。

    展开全文
  • 今天小编就为大家分享一篇python实现在一个画布上多个子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 当我们把设计稿和技术选型定下来之后,接下来就要开始着手这个依赖了。依赖的组成最简单的就是节点Node 和节点之间的连线。这一节我们要处理的就是节点位置信息的处理。为了确定节点的位置信息,首先要给节点...

    概述

    当我们把设计稿和技术选型定下来之后,接下来就要开始着手画这个依赖图了。依赖图的组成最简单的就是节点Node 和节点之间的连线。这一节我们要处理的就是节点位置信息的处理。为了确定节点的位置信息,首先要给节点分层,分层的信息取决于节点之间的依赖关系。

    问题分析

    当前我们默认图是从上到下布局方式,节点分层,最容易想到的就是拓扑排序,通过BFS 宽度优先遍历,计算每个节点的步长。

    自顶向下BFS

    如上图,我们如果是普通的BFS,我们会发现D 节点应该是第二层,实际上D应该是第三层,所以,实际上每个节点应该取最大的步长,实现如下

    function calcLayer(nodes){
        var queue = [];
        var maxLayer = 1;
        var nodesT =  nodes;
            //  入度为0 的点放入队列
        for (var i = 0; i < nodesT.length; i++) {
            if (nodesT[i].inputNode.length === 0) {
                nodesT[i].layer = 1;
                queue.push(nodesT[i]);
            }
        }
        while(queue.length > 0){
            var tNode = queue.shift();
            if (tNode.outputNode && tNode.outputNode.length > 0) {
                for (var j = 0; j < tNode.outputNode.length; j++) {
                    var outputNodeIndex = getNodeIndex(tNode.outputNode[j], nodesT);
                    if(outputNodeIndex < 0) continue;
                    if (nodesT[outputNodeIndex].layer === -1) {
                        nodesT[outputNodeIndex].layer = tNode.layer + 1;
                    }else{
                        nodesT[outputNodeIndex].layer = Math.max(nodesT[outputNodeIndex].layer,tNode.layer + 1);
                    }
                    // 更新节点层次,选择最大值
                    maxLayer = Math.max(maxLayer, nodesT[outputNodeIndex].layer);
                    queue.push(nodesT[outputNodeIndex]);
                }
            }
        }
    }

    至此分层基本完成,但是发现另外一种情况,如下:

    如果是按照刚才那种分发,入度为0 的节点必然在第一层,其实这种,我们可能更希望 G在第三层,F 在第二层,例如下图展示的

    这样展示,图会更紧凑一点,观察图可知,在链路中间的节点,它的层级就是固定的,例如D节点,但是一些没有上游节点的,例如G,F 的位置确实可以多种选择的。在观察,我们可以知道,如果从E往上BFS,我们会发现,G,F节点就是我们需要的层次了,所以,这时候,我们需要自底向上再进行一次BFS,得到新的层级,并且用自底向上的结果去矫正自上而下的结果,这一点很关键。

    自底向上BFS

    function bottomToTop (nodes){
        var queue = [];
        var maxLayer = 1;
        for (var i = 0; i <nodes.length; i++) {
            if (nodes.outputNode.length === 0) {
                nodes[i].layer = 1;
                queue.push(nodes[i]);
            }
        }
        while(queue.length > 0){
            var tNode = queue.shift();
            if (tNode.inputNode && tNode.inputNode.length > 0) {
                for (var j = 0; j < tNode.inputNode.length; j++) {
                    var inputNodeIndex = getNodeIndex(tNode.inputNode[j], nodes);
                    if(inputNodeIndex < 0) continue;
                    if (nodes[inputNodeIndex].layer === -1) {
                        nodes[inputNodeIndex].layer = tNode.layer + 1;
                    }else{
                        nodes[inputNodeIndex].layer = Math.max(nodes[inputNodeIndex].layer,tNode.layer + 1);
                    }
                    maxLayer = Math.max(maxLayer, nodes[inputNodeIndex].layer);
                    queue.push(nodes[inputNodeIndex]);
                }
            }
        }
            // 计数从下到上的,这里要转换成从上到下
        for(var i = 0; i < nodes.length; i++){
            nodes[i].layer = maxLayer + 1 - nodes[i].layer;
        }
    
    }

    修正结果集

    function fixLayer (nodesT, nodes){
        for(var i = 0; i < nodesT.length; i++){
            if(nodesT[i].inputNode.length === 0){
                var minL = maxLayer;
                var minT = maxLayer;
                if(nodesT[i].outputNode && nodesT[i].outputNode.length > 0){
                    for(var j = 0; j < nodesT[i].outputNode.length; j++){
                        var inputNodeIndex = getNodeIndex(nodesT[i].outputNode[j], nodes);
                        var inputNodeIndexT = getNodeIndex(nodesT[i].outputNode[j], nodesT);
                        if(inputNodeIndex < 0) continue;
                        if(inputNodeIndexT < 0) continue;
                        //  注意,矫正的结果不能该节点比它子节点的层级还要高,这里要和它子节点做一次比较
                        minL = Math.min(minL, nodes[inputNodeIndex].layer - 1);
                        minT = Math.min(minT, nodesT[inputNodeIndexT].layer - 1);
                    }
                }
                nodesT[i].layer = Math.min(minL,minT);
            }
        }
    
    }

    总结

    这里主要用到了BFS,如果很熟悉这个算法的话,还是很简单的,同时要观察一些实际情况,做一些优化即可!

    本文由华为云发布。

    展开全文
  • python制作DAG图,生成邻接矩阵

    千次阅读 2019-03-08 19:44:06
    主要方法是运用Top排序倒退回来,这里我考虑到节点和和边内容的一些关系,假设有n个节点,这里控制边的个数为[n-1,(n*(n-1)/2+1] from random import ... np.savetxt("DAG.txt",DAG,fmt='%.2f') DataMake()
  • 编译原理-----DAG图

    千次阅读 2019-11-26 16:21:42
    题目: DAG优化 Time Limit:1000 msMemory Limit:65536 KiB Problem Des...
  • 《编译原理》 DAG 与求优化后的 4 元式代码- 例题解析
  • 有向循环的绘制(DAG

    千次阅读 2020-07-20 19:42:44
    绘图网站http://www.dagitty.net/ R包:dagitty; ggdagis a nice R package based on dagitty but tidyverse-compatible and with much better plotting functionality.
  • 目的用Java或则PythonDAG图、状态转换应用场景:编译原理等课程设计 自动机、状态转换、基本块等有向效果如下:1.安装1.需要用到 graphviz,需要下载 http://www.graphviz.org/download/但是上面下载太...
  • DAG(Directed Acyclic Graph),即有向无环,常常用来描述工程和系统的进行过程。比如: 工程能否顺利进行(有向的拓扑排序问题) 估算工程完成必须的最短时间(有向的关键路径问题) 对于DAG图来说,我们...
  • 我们可以模拟这个过程: 如果p1 所在直线没有被最近的下一层挡住,也就是中D,E,F节点挡住的话,那就说明,起点可以先到p2 到p2 之后,继续判断第三层节点,由于B节点会挡住从p2 往下的竖线,所以绕开B节点...
  • 然后,每个作业以依赖定义的顺序启动一系列任务(子进程),您可以通过Web界面中的点击拖动轻松绘制.这是与以下三个方面相比最轻巧的调度程序.2)在ETL任务方面,Spotify开放的luigi更侧重于hadoop...
  • nmf的matlab代码概要 卡内基梅隆大学的团队在旧版Graphlab中实现了学习算法。 超参数优化尚未实现。 有关更多信息,请访问上述链接。 例子 小型合成数据集上的分解结果如下所示...DAG约束变分贝叶斯NMF”。 [在线的]。
  • 因为研究方向设计到依赖性的应用,做实验需要用到一些随机的DAG(有向无环)拓扑来作为应用的表示,找了找网上没有符合的代码,于是决定自己写个小脚本来生成大量随机的DAG拓扑。 我实验中要用到的依赖性应用拓扑...
  • python实现有向无环(DAG)

    千次阅读 2021-04-13 19:30:30
    from collections import OrderedDict, defaultdict from copy import copy, deepcopy class DAG(object): """ Directed acyclic graph ... """ Construct a new DAG with no nodes or edges. """ self.reset
  • 表示因果的三种DAG, MAG and PAG

    千次阅读 2021-04-26 20:53:32
    表示因果的三种DAG, MAG and PAG1. DAG2. MAG(maximal ancestral graph)2.1 Mixed Graphs2.2 Ancestral Graphs2.3 Maximal Ancestral Graphsreferences 1. DAG Directed acyclic graph,最常见的一种。...
  • 有向无环 邻接表实现 package main import ( "fmt" "github.com/eapache/queue" ) // 邻接表 type Vertex struct { Key string Parents []*Vertex Children []*Vertex Value interface{} } type DAG ...
  • 背景在阿里云,可能每时每刻都在身边充斥着“数据表”“模型”“调度”等等抽象概念。作为阿里云的前端,如何将这种抽象的概念实体化可视化地给客户或者其他合作伙伴展示出来显得尤为重要。...“流程...
  • 方法1(暴力方法)假设需要生成一个n个节点,m条边的DAG,那我们可以先生成一个的,元素均为0的矩阵,从个位置中,随机抽取m个位置等于1,这样就构造出了一个的邻接矩阵。随后,我们再分别写两个程序,一个用来验证...
  • E + C:E:D) class(graph) plot(graph) 或者运行以下代码绘制有向无环dag) library("gRbase") graph (~A:B:E + C:E:D) dag <-dag(~A + B:A + C:B + D:B + E:C:D) plot(dag) 注意观察在dag中 a:b 中第一个节点a是...
  • 有向的强连通分支及DAG

    千次阅读 2014-08-06 09:00:50
    有向的强连通分支:在有向G中
  • Description 大家都学过了代码优化,其中有一个DAG优化,这次我们就...通过构造DAG图,进行代码优化,只需要保留AB,删除无用变量,删除变量时,尽量保留最早出现的变量。 PS:保证AB的值不同 Sample Input 3..
  • 4.渲染dag图。 var render = new dagreD3.render(); var svg = d3.select( "#svgCanvas" ); //声明节点 svg.select( "g" ).remove(); //删除以前的节点,清空画面 var svgGroup = svg.append( "g" )...
  • 最近需要将深度学习网络结构以比较直观的形式在前端显示出来,对于数据的传输,计划使用数据路径流动的形式来做,因为我也没怎么学过前端变成,对于自己写js和css代码,感觉之后会出不少问题,前端显示的美观度,...
  • 有向无环Directed Acyclic Graph(DAG

    千次阅读 2021-08-01 17:08:18
    有向无环Directed Acyclic Graph(DAGDAG是一个没有 有向循环的、有限的有向 。 它由有限个顶点和有向边组成,每条有向边都从一个顶点指向另一个顶点; 从任意一个顶点出发都不能通过这些有向边回到原来的...
  • Python 数据结构与算法——拓扑排序 几乎在所有的项目,甚至日常生活,待完成的不同任务之间通常都会存在着某些依赖关系...对于这种依赖关系,很容易将其表示成一个有向无环(Directed Acyclic Graph,DAG,无环...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,042
精华内容 1,216
关键字:

dag图怎么画