我遇到了这个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;
}
答案 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)时间复杂度。
此外,算法仍然不正确。给定包含节点A
和B
以及单个边B->B
的图表,isCyclicDirected(A)
将永远不会检测到周期。
答案 2 :(得分:0)
回溯才是必不可少的。你的算法会在上一个答案中提到的情况下发现误报: A - &gt;乙 \ ^ v | C 但是如果你添加回溯你的alto将完美地工作,即使在上面的情况下。