在有向图中使用DFS进行循环检测是否绝对需要回溯?

时间:2013-12-12 22:10:43

标签: algorithm depth-first-search backtracking directed-graph cyclic

我遇到了这个SO post,其中建议在有向图中使用DFS进行循环检测由于回溯而更快。我在这里引用该链接:

  

深度优先搜索比广度优先搜索更有效,因为您可以更快地回溯。如果使用调用堆栈,它也更容易实现,但这依赖于没有溢出堆栈的最长路径。

     

此外,如果您的图表是定向的,那么您不仅要记住       如果你曾经访问过一个节点,那么你是如何到达那里的。       否则你可能会认为你已经找到了一个循环,但实际上       所有你拥有的是两条独立的路径A-> B但这并不意味着       存在路径B-> A。通过深度优先搜索,您可以标记节点       当你下降时访问它并在你回溯时取消标记它们。

为什么回溯必不可少?

有人可以用示例图解释给定A->B示例中的含义吗?

最后,我在有向图中有一个DFS代码用于循环检测,该代码不使用回溯但仍检测O(E+V)中的循环。

public boolean isCyclicDirected(Vertex v){
  if (v.isVisited) {
    return true;
  }
  v.setVisited(true);
  Iterator<Edge> e = v.adj.iterator();
  while (e.hasNext()) {
    Vertex t = e.next().target;
    // quick test:
    if (t.isVisited) {
      return true;
    }
    // elaborate, recursive test:
    if (isCyclicDirected(t)) {
      return true;
    }
  }
  // none of our adjacent vertices flag as cyclic
  v.setVisited(false);
  return false;
}

3 个答案:

答案 0 :(得分:5)

为什么需要回溯:

A -> B
^ \
|  v
D <- C

如果你去A -> B而你没有回溯,那么你就会停在那里,你将找不到这个循环。

您的算法确实回溯了。你只是将它包装在递归中,所以它可能看起来不像你期望的那样。你为其中一个邻居递归,如果没有找到一个循环,那个呼叫会返回,你会尝试其他邻居 - 这就是回溯。

为什么你需要记住你到达目的地的方式:

A -> B
  \  ^
   v |
     C

上图没有周期,但是如果你去A -> B,然后A -> C -> B,你会认为如果你不记得路径。

如链接帖子中所述,您可以在返回代码之前将被访问标志设置为false(我看到您现在已经完成了) - 这将用作记住路径。

答案 1 :(得分:1)

值得指出的是,这种标记算法是对链接列表周期检测的简单方法的改编,其涉及跟踪到目前为止所访问的每个节点。在这种情况下,递归后面的路径被视为链表,并应用链表算法。空间复杂性使得该算法对于链表而言是次优的,因为您需要保持对列表中每个节点的引用,它是O(n)。当你将它应用于一个体面的平衡图时,空间复杂度下降到O(logn)。在图形是树的情况下,空间复杂度降低到O(n),但是你得到O(n)时间复杂度。

此外,算法仍然不正确。给定包含节点AB以及单个边B->B的图表,isCyclicDirected(A)将永远不会检测到周期。

答案 2 :(得分:0)

只有当您的图表没有任何情况可以通过两个不同的路径从节点A到达节点B时,

回溯才是必不可少的。你的算法会在上一个答案中提到的情况下发现误报: A - &gt;乙   \ ^    v |      C 但是如果你添加回溯你的alto将完美地工作,即使在上面的情况下。