上一篇中,我们介绍了ArangoDB中图遍历的基础语法,并编写了最简单的遍历深度为1的语句,这一篇将在图遍历主题上更进一步,介绍ArangoDB中图遍历的各种选项。
遍历选项
用户可以利用这些选项,来更精确地控制遍历行为
深度/广度优先搜索
一旦遍历深度>=2时,就涉及到了该如何遍历,即这里指的遍历选项,是用深度优先DFS(Depth First Search)呢? 还是用广度优先 BFS(Breadth First Search)?
- DFS(默认):从起点依据遍历深度一条路走到黑,然后再换一条路径
- BFS:先遍历深度较低的全部节点,然后逐步增加深度,像是地毯式搜索
如果遍历的其他选项都一样,则这两种搜索方式会返回同样数量的路径,区别仅在于边和顶点被访问到的次序
如果BFS跟filters和 limits 搭配使用,在未达最大遍历深度之前就停止遍历,则可以显著提升遍历效率。
请看下图的例子,我们要从S开始,进行1..10深度的遍历,找到满足条件的某个节点,此处假设要找F顶点。如果使用DFS,则遍历可能会从A开始,然后一路沿着D,G往深走10跳到达最深处,然后是E,H这条路径;但使用BFS时,如果你限制只取一个匹配,在深度为2时找到F顶点后,就不会再去搜索更深的顶点。
FOR v IN 1..10 OUTBOUND 'verts/S' edges
OPTIONS {bfs: true}
FILTER v._key == 'F'
LIMIT 1
RETURN v
Uniqueness 选项
图上的某两个顶点之间,很多时候并不是仅有一条路径可达,前文提到过ArangoDB支持各种类型、各种体量的图,有时候图中会出现环,比如下图中 S->B->C->S就是一个环。
- 默认情况,为了避免遍历时陷入一个环而无法到达最大深度,当一条边已经被经过而又再次出现时,遍历会停止。
- 除非另有配置,否则允许一条路径上出现重复的顶点。
图1:从S出发的遍历
来看一个例子:
# 为了助于理解,此查询中的两个选项都是默认的,但我们依旧明确写出来
# 使用了路径变量p, 将每条路径上的顶点,用其._key形成一个字符串,比如 S->A->D->E
FOR v, e, p IN 1..5 OUTBOUND 'verts/S' edges
OPTIONS {
uniqueVertices: 'none',
uniqueEdges: 'path'
}
RETURN CONCAT_SEPARATOR('->', p.vertices[*]._key)
上述查询共找到10条路径:
- S->A
- S->B
- S->A->D
- S->B->D
- S->B->C
- S->A->D->E
- S->B->D->E
- S->B->C->S
- S->B->C->S->A
- S->B->C->S->A->D
其中 S->B->C->S路径中开始和终止都是S节点,这是合理的,因为 uniqueVertices 选项没有做规定。
而 S->B->C->S->B->C没出现在查询结果中,因为选项规定一条路径中不能出现重复的边,如果选项是 uniqueEdges: 'none', 则这条带重复边的路径,恰好其深度为5,符合我们的查询要求,就可以出现在结果中。 如果查询要求的最大深度更高,则整个遍历的结果可能会由于有个环而产生很多路径结果。
如果想防止某个顶点在遍历中被访问多次,有两种可行方式:
- uniqueVertices: 'path': 保证单个路径中不存在重复顶点
- uniqueVertices: 'global':更严苛,整次遍历中,每个可达的顶点仅被遍历一次
此处需要 bfs:true,不支持dfs,因为dfs的结果有不确定性(多次运行结果不一致),根本原因是并没有一个规则告诉遍历器,当面前同时有几条路时,先走哪条,后走哪条。例如前面的图1,有多条路可从S->E,由于没有明确顺序,遍历器会随机选一条路. 这就会导致多次运行的结果可能不一致。
动手实践
机场航班数据集中,我们想查询从某个机场出发,所有可直达的机场
# 从洛杉矶LAX机场可直达的所有机场,
# 方法1,我本机运行时间约1.1ms
FOR airport IN OUTBOUND 'airports/LAX' flights
OPTIONS {bfs: true, uniqueVertices: 'global'}
RETURN airport
# 方法2, 我本机运行时间约为 45ms, 二者结果一样,效率差别很大
FOR airport IN OUTBOUND 'airports/LAX' flights
OPTIONS {bfs: true}
RETURN DISTINCT airport
上述两种方法的效率差别很大,原因是 RETURN DISTINCT 只有在遍历将所有的顶点返回之后(因为两个机场之间有很多航班可以通航,所以有巨大的中间结果),才会去重;而 uniqueVertices: 'global' 是个遍历选项,它会指引遍历器在遇到重复顶点时,马上忽略。