是否有一种有效的方法来确定叶节点是否可以从有向非循环图中的另一个任意节点到达?

时间:2009-05-28 15:45:22

标签: algorithm optimization graph directed-acyclic-graphs

Wikipedia: Directed Acyclic Graph

不确定叶子节点是否仍然是正确的术语,因为它不是真正的树(每个节点可以有多个子节点,也有多个父节点),而且我实际上也在尝试查找所有根节点(这实际上只是一个问题)语义,如果你反转所有边的方向它们就是叶子节点。)

现在我们只是遍历整个图形(可以从指定节点到达),但结果有点贵,所以我想知道是否有更好的算法来做到这一点。我正在考虑的一件事是我们跟踪已经访问过的节点(在遍历不同的路径时)并且不重新检查那些节点。

还有其他算法优化吗?

我们还考虑过保留这个节点是其后代的根节点列表,但是如果我们需要在每次添加节点时检查它是否发生变化,维护这样的列表似乎也相当昂贵,移动或移除。

编辑:

这不仅仅是查找单个节点,而是查找作为端点的所有节点。

此外,没有主节点列表。每个节点都有一个孩子的列表和它的父母。 (嗯,这不是完全正确的,但提前从数据库中提取数百万个节点的成本非常高,可能会导致OutOfMemory异常)

EDIT2:

可能会或可能不会改变可能的解决方案,但是图表底部沉重,因为最多只有几十个根节点(我正在尝试查找)和一些数百万(可能是数十或数亿)的叶节点(我从哪里开始)。

3 个答案:

答案 0 :(得分:3)

根据您的结构,有一些方法可能会更快,但一般来说,您想要的是遍历。

深度优先搜索,遍历每条可能的路线,跟踪已经访问过的节点。它是一个递归函数,因为在每个节点上你必须分支并尝试它的每个子节点。如果你不知道哪种方式来寻找你必须尝试的对象,那么没有更快的方法!你肯定需要跟踪你已经去过的地方,否则会浪费。它应该要求按节点数量的顺序进行完全遍历。

广度优先搜索类似但在“继续”之前访问节点的每个子节点,因此构建了与所选根的距离层。如果预期目标接近根节点,则可以更快。如果预期它会一直沿着一条路走下去会更慢,因为它会迫使你遍历每一条可能的边缘。

你可能保留一个已知根节点的列表是正确的,权衡的是你必须在每次改变图形时进行搜索。如果您正在改变图表很少这是可以接受的,但如果您更频繁地更改图表而不是生成此信息,那么当然它太昂贵了。

编辑:信息更新。 听起来我们实际上正在寻找两个任意节点之间的路径,根/叶语义不断被切换。 DepthFirstSearch(DFS)从一个节点开始,然后为每个未访问的子节点递归。如果找到目标节点,则中断。由于递归计算的方式,这将沿着“左”路径一直遍历,然后在到达“正确”路径之前枚举此距离处的节点。如果目标节点可能是右侧的第一个子节点,则时间成本高且效率低。 BreadthFirst步行,覆盖所有孩子,然后前进。因为你的图表像树一样重,所以两者的执行时间大致相同。

当图表重重时,您可能对反向遍历感兴趣。从目标节点开始向上走,因为此方向上的节点相对较少。只要节点一般拥有比子节点更多的父节点,这个方向就会快得多。您还可以组合这些方法,逐步向上一步,然后比较节点列表,并在中间某处进行会议。 (如果忽略每一步完成两倍的工作,这种组合似乎是最快的。)

但是,由于您说您的图表存储为子项列表的列表,因此您没有真正的向后遍历图表的方法。节点不知道它的父母是什么。这是个问题。要修复它,你必须通过在图形更新中添加数据,或者通过创建整个结构的副本(你说的太大)来让节点知道它的父节点是什么。它需要重写整个结构,这听起来可能是不可能的,因为它在这一点上是一个大型数据库。   还有很多工作要做。 http://en.wikipedia.org/wiki/Graph_(data_structure)

答案 1 :(得分:2)

只需对访问过的节点进行着色(跟踪)。

Python中的示例:

def reachable(nodes, edges, start, end):
  color = {}
  for n in nodes:
    color[n] = False
  q = [start]
  while q:
    n = q.pop()
    if color[n]:
      continue
    color[n] = True
    for adj in edges[n]:
      q.append(adj)
  return color[end]

答案 2 :(得分:0)

对于要计算位数f(x)的顶点x,每个位对应一个根顶点Ri,而1(resp 0)表示从根顶点Ri到达“x can(resp不能)

您可以将图形划分为一个“上”集U,其中包含所有目标根R,并且如果x在U中,那么x的所有父项都在U中。例如,距离<= D的所有顶点的集合离最近的Ri。

保持U不要太大,并为U的每个顶点x预先计算f。

然后,对于查询顶点y:如果y在U中,则您已经有了结果。否则递归执行y的所有父项的查询,缓存每个访问的顶点x(例如在地图中)的值f(x),因此您不会计算两次值。 f(y)的值是其父项值的按位OR。