有向无环图的人类可读文本表示

A human-readable textual representation of a Directed Acycling Graph

一棵树有一堆人类和机器可读的文本表示——例如嵌套列表(以各种表示形式——例如 JSON 和 YAML)和 XML。结合缩进,它们使想象结果结构变得非常容易。

但我没有看到 Directed Acyclic Graph 具有相同级别的可读性。它是比树更通用的数据结构,因此不能使用上述格式(无论如何逐字)。

我想到的应用程序是各种流程图——例如自然而然地出现在各种规划任务中。


为了限制问题的范围,我主要要求的是标准解决方案,或者至少是生产就绪的并且在某些实践领域被证明有效。如果有 none,任何通过某种同行评审的实验命题(例如在已发表的科学论文中提出的)都必须这样做。

git log --graph 表示以文本方式提交的有向图。 (在 Git 中,多个提交可以从单个提交中分支出来,并且两个提交可以合并为一个新提交,从旧提交到新提交的时间可以是一个方向。所以它是一个没有循环的有向图)

--format...%p 包括父提交,因此它可能被认为是机器可读的。例如

git log --graph --abbrev-commit --decorate --format=format:'%h - %aD %p %s'

会显示如下内容:

*   585a502 - Mon, 23 Jul 2012 19:13:28 +0100 1ce012a 00b3327 Merge github.com:orangeduck/CPlus
|\
| *   00b3327 - Mon, 23 Jul 2012 07:38:16 -0700 9a24ec6 5c5b6e2 Merge pull request #4 from felipecruz/feature/create_tests_dir
| |\
| | * 5c5b6e2 - Mon, 23 Jul 2012 10:04:07 -0300 11fb96d move tests to tests dir
| | * 11fb96d - Sun, 22 Jul 2012 18:19:51 -0300 9a24ec6 fix variable name in Strind_Discard
| |/
* | 1ce012a - Mon, 23 Jul 2012 19:13:05 +0100 9a24ec6 Array instances Push. Used memmove for dynamic container manipulation.
|/
* 9a24ec6 - Sun, 22 Jul 2012 14:02:24 +0100 b41f9c2 Better documented source
* b41f9c2 - Sun, 22 Jul 2012 12:50:13 +0100 2f4b862 Assign class. Added WIP Array Type.

其中 * - 伪图形中的节点、线和随后的提交 - 方向从较早(底部)到较新(顶部)的边缘

更新

非平面 DAG 在这里看起来笨拙得多。非平面示例,与维基百科中的 this 示例相同:

*   094f405 - Thu, 19 Sep 2019 03:51:50 +0300 ab942ca 76ee1ad X, Y, Z
|\
| *   76ee1ad - Thu, 19 Sep 2019 03:49:49 +0300 b1ea78b 8d32234 Y, Z
| |\
* | \   ab942ca - Thu, 19 Sep 2019 03:51:17 +0300 b20cf0b 7f714d9 XY, XZ
|\ \ \
| * \ \   7f714d9 - Thu, 19 Sep 2019 03:49:05 +0300 b84010c 8d32234 X, Z
| |\ \ \
| | | |/
| | |/|
| | * | 8d32234 - Thu, 19 Sep 2019 03:45:18 +0300 6927afa Z
* | | |   b20cf0b - Thu, 19 Sep 2019 03:47:04 +0300 b84010c b1ea78b X, Y
|\ \ \ \
| |/ / /
|/| | /
| | |/
| |/|
| * | b1ea78b - Thu, 19 Sep 2019 03:44:40 +0300 6927afa Y
| |/
* | b84010c - Thu, 19 Sep 2019 03:43:53 +0300 6927afa X
|/
* 6927afa - Thu, 19 Sep 2019 03:31:01 +0300 4bf9923 #4

还有很多 markdowns 支持有向图,例如this one(谷歌搜索的第一个)

图形的一个很好的文本表示是 dot language from graphviz。语法是易于阅读的关系描述。

请注意,graphviz 背后的核心思想不是从图表开始描述它,而是不知道图表是什么样子,从你知道的开始,然后让 graphviz 生成图给你。由于这个设计目标 graphviz 没有任何手动放置节点的功能 - 它会根据您 select.

的算法自动绘制节点

这是使用 graphviz 的示例:

Assume you want to draw an org chart of a company. You don't know what the graphs look like yet but you know who reports to who. You can describe the company as follows:

digraph {
    CEO                ->  Board_of_Directors
    CTO                ->  CEO
    CFO                ->  CEO
    COO                ->  CEO
    DevLead            ->  CTO
    DevTeam            ->  DevLead
    DevOps             ->  CTO
    Head_of_Accounting ->  CFO
    Accountants        ->  Head_of_Accounting
    Procurement        ->  Head_of_Accounting
    Procurement        ->  COO
    Logistics          ->  COO
    Tech_support       ->  CTO
    Tech_support       ->  COO
}

运行 这与 dot 算法将生成以下图表:

Graphviz 确实具有复杂的节点和边描述功能,例如定义节点的形状、边的标签等。但是添加此类细节通常会稍微降低图形的可读性,因为现在大部分代码看起来像样式定义而不是节点之间的关系。不过,我认为图形定义本身相当清晰。与任何语言一样,它旨在用于解决人类问题(在这种情况下,如果您知道状态和转换,则弄清楚图形的样子)并且必须至少在某种程度上可用于其预期用途。

在形式语言和自动机理论中,最重要的结果之一是确定性有限自动机和(形式)正则表达式之间的等价性。 DFA 只是带有一些额外信息的标记有向图。我提出以下建议:(1)考虑 DAG 中的所有状态都接受(2)用该弧开始的顶点的标签标记每个弧(3)为这个 DFA 产生一个正则表达式(选择拓扑最小状态作为不同 DFA 的起始状态)。首先,您需要对顶点进行拓扑排序,然后为每个连接的组件创建一个 DFA/regular 表达式对。

示例:节点 A 转到节点 B 和 C,B 转到 D 和 E,C 转到 E 和 F。

拓扑排序发现按标签字母顺序排列的顶点已经排序:A、B、C、D、E。从拓扑最小节点A开始有一个连通分量。

在标记圆弧后通过算法可能会给你一个像 A(B(D+E)+C(E+F)) 这样的正则表达式。

请注意,由于图形是无环的,因此您永远不需要 Kleene 闭包符号 asterisk/star。如果有兴趣,可以详细说明这个答案。

编辑:一些详细说明。

评论中指出,上述正则表达式中存在一些重复。这是真实的。然而,这可能不会变成灾难性的重复:我们可以避免重复子图,至少在某种程度上是这样。例如,假设在上面的例子中有一个长子图,其拓扑最小节点 E。假设它有可接受的正则表达式 r。那么我们可以将上面的正则表达式调整为:A((B+C)Er + BD + CF)。仍然存在重复,现在在 B 和 C 而不是 E 之间,但是由于子图具有拓扑最小节点 E,这仍然是更简洁的表示。

最小化通用正则表达式是 PSPACE 完全的。但是,我不知道这个界限是否适用于最小化从图是 DAG 的 DFA 生成的正则表达式。正如评论正确指出的那样,DFA 和 RE 的常规理论处理比 DAG 更复杂的一般有向图。完全有可能没有 Kleene 星号的正则表达式比有 Kleene 星号的正则表达式更容易最小化。这可能是一个值得单独提出的问题,可能在 cs.stackexchange.com.

表示 DFA 的另一种常用方法是使用常规语法。这本质上等同于简单地列出对应于 DFA 状态图中弧的有序节点对。

EDIT2:一个例子

另一个答案有这个例子:

A->B
A->C
A->D
B->E
B->F
C->E
C->G
D->F
D->G
E->H
F->H
G->H

我怀疑此处描述的几乎最小的正则表达式大致如下:A(B(E+F)+C(E+G)+D(F+G))H。我们的表示比较如下:

  • 等价语法中有 24 个符号,但正则表达式中有 11 个符号
  • 12 个等价语法运算符,但 8 个正则表达式运算符
  • 36 vs 19 个令牌

很难与可见性图表进行比较,因为表示方式如此不同,但正则表达式显然使用了更少的符号总数(如果我们计算符号的话)。

EDIT 3:又提出了一个例子,如下:

a -> b
a -> c
a -> d
b -> c
b -> e
c -> f
d -> e
d -> f
d -> g
e -> f
e -> h
f -> i
g -> h
g -> i
h -> i

我可以手工想出的最简洁的正则表达式(第二次猜测,没有方法论)是这样的:a((b+d)e(f+h)+(bc+c+d)f+ dg(h+#))i 。请注意,# 不是通常的正式正则表达式语法的一部分,它表示空正则表达式,即它生成由空字符串组成的语言。这是一种方便的方式,可以更好地进行最小化,并且不会增加计算能力,只会增加表达能力。如果你不喜欢它,你可以用 dgh + dh 代替,它只长一个符号。这种表示仍然是原始语法大小的一半左右。实际上,语法和可见性图的比较是相似的w.r.t。最后一个例子的那些。

编辑 4:我现在将扩展上一个示例中的表达式,以表明它是通过 DAG 的不相交路径并集的分解表示。

a((b+d)e(f+h)+(bc+c+d)f+dg(h+#))i
a((b+d)e(f+h)+(bc+c+d)f+dgh+dg)i
a((b+d)ef+(b+d)eh+(bc+c+d)f+dgh+dg)i
a(bef+def+beh+deh+(bc+c+d)f+dgh+dg)i
a(bef+def+beh+deh+bcf+cf+df+dgh+dg)i
abefi+adefi+abehi+adehi+abcfi+acfi+adfi+adghi+adgi

这是可见性表示的简单变体。请参阅:平面正交和折线 绘图算法 部分:7.2.3 可见性表示

A -> B
A -> C
A -> E
B -> D
B -> F
C -> D
C -> G
D -> H
E -> F
E -> G
E -> G
F -> H
 G--------G--------G                
 |        |        |        
 |     H--H--H     |    
 |     |     |     |
 | F---F     D---D |     
 | |   |     |   | |   
 E-E   B--B--B   C-C  
   |      |      |       
   A------A------A

这里是this

A -> B
A -> C
A -> D
B -> E
B -> F
C -> E
C -> G
D -> F
D -> G
E -> H
F -> H
G -> H

)|(表示桥接,无连接。

          H--H--H          
          |  |  |           
  E-------E  |  G----G--G   
  |       |  |  |       |   
  |  F---)|(-F-)|(---F  |   
  |  |    |     |    |  |   
  B--B    C--C--C    D--D   
  |          |          |   
  A----------A----------A 

来自:Orthogonal Graph Drawing with Constraints图3.4(a)

a -> b
a -> c
a -> d
b -> c
b -> e
c -> f
d -> e
d -> f
d -> g
e -> f
e -> h
f -> i
g -> h
g -> i
h -> i
         i---i-------i   
         |   |       |       
 f---f---f   h---h   |   
 |   |   |   |   |   |   
 |   |   e---e   g---g  
 |   |   |   |   |       
 |   d---d--)|(--d       
 |           |   |        
 c---c       |   |        
 |   |       |   |        
 |   b-------b   |        
 |   |           |        
 a---a-----------a    

我将使用 带有锚点的 YAML 嵌套列表。 (这相当于 XML 与实体,但后者有更多的噪音。)
(我已经在考虑了,但想知道是否发明了更好的东西。看起来还没有。但最重要的是,@Patrick87 正式表明它是一个足够的代表。)

相当于如果我用缩进替换合成和没有缩进的联合;并且当节点被多次引用时,锚点允许消除节点下子图的重复。


例如@GuyCoder 的例子

A->B
A->C
A->D
B->E
B->F
C->E
C->G
D->F
D->G
E->H
F->H
G->H

对应A(B(E+F)+C(E+G)+D(F+G))HA(B(EH+FH)+C(EH+GH)+D(FH+GH))

将是

- A
    - B
        - &E E
            - &H H
        - &F F
            - *H
    - C
        - *E
        - &G G
            - *H
    - D
        - *F
        - *G

(为了统一起见,如果生成它,每个原始节点都可以作为锚点。)


我不在乎图表是否是平面的,因为任何交叉链接都不是 "drawn"。

作为奖励,它允许指定附加到每个节点的数据,作为以节点为根的散列table。 (虽然超过了一定的大小,但分开放置数据可能会更清晰。)