为什么用DFS而不是BFS来查找图中的循环


Answers:


73

深度优先搜索比深度优先搜索具有更高的内存效率,因为您可以更快地回溯。如果使用调用堆栈,则实现起来也更容易,但这取决于最长的路径而不会使堆栈溢出。

同样,如果图形是有向的,那么您不仅要记住是否访问过某个节点,还必须记住如何到达该节点。否则,您可能会认为您已经找到了一个循环,但实际上,您只有两个单独的路径A-> B,但这并不意味着存在路径B-> A。例如,

如果从开始执行BFS 0,它将检测到存在周期,但实际上没有周期。

通过深度优先搜索,您可以在下降时将节点标记为已访问,并在回溯时将其取消标记。有关此算法的性能改进,请参见评论。

对于检测有向图中的周期最佳算法,您可以看一下Tarjan算法


3
(内存有效,因为您可以更快地回溯,并且更容易实现,因为您可以让堆栈负责存储打开列表,而不必显式维护它。)
琥珀色

3
IMO,只有依靠尾递归,这才容易。
汉克·盖伊

2
“在回溯时取消标记”-后果自负!这很容易导致O(n ^ 2)行为,特别是这样的DFS会将交叉边缘误解为“树”边缘(“树”边缘也将被误用,因为它们实际上不再形成树了)
Dimitris Andreou

1
@Dimitris Andreo:您可以使用三个访问状态而不是两个来提高性能。对于有向图,“我之前见过此节点”和“此节点是循环的一部分”之间是有区别的。对于无向图,它们是等效的。
Mark Byers 2010年

确实,您肯定需要第三个状态(以使算法线性化),因此您应该考虑修改该部分。
Dimitris Andreou 2010年

28
  1. DFS易于实施
  2. DFS找到一个周期后,堆栈将包含形成该周期的节点。BFS并非如此,因此,如果您还想打印找到的循环,则需要做一些额外的工作。这使DFS更加方便。

10

如果图形是无向的,则BFS可能是合理的(我的客人在使用BFS展示一种有效的算法,该算法将在有向图中报告周期!),其中每个“交叉边”都定义了一个周期。如果交叉边缘为{v1, v2},并且包含这些节点的根(在BFS树中)为r,则循环为r ~ v1 - v2 ~ r~是路径,-单个边缘),可以像在DFS中一样容易地报告。

使用BFS的唯一原因是,如果您知道您的(无向)图将具有较长的路径和较小的路径覆盖率(换句话说,深而窄)。在这种情况下,BFS队列所需的内存将比DFS堆栈所需的内存成比例地减少(当然两者仍然是线性的)。

在所有其他情况下,DFS显然是赢家。它适用于有向图和无向图,并且报告周期很简单-只要连接任何后缘到祖先到后代的路径,就可以得到周期。总而言之,此问题比BFS更好,更实用。


4

BFS不会在寻找周期中使用有向图。将A-> B和A-> C-> B视为图中从A到B的路径。BFS会说,沿着B被访问的路径之一。当继续沿下一条路径行驶时,它将说再次找到了标记的节点B,因此存在一个循环。显然这里没有周期。


您能否解释一下DFS如何清楚地识别出示例中不存在该循环。我同意所提供的示例中不存在该循环,但是如果我们从A-> B然后再从A-> C-> B中找到B已经被访问并且其父代是A而不是C ..我读到DFS将通过比较已经访问过的元素的父代与当前正在检查的方向的当前节点来检测周期。我弄错了DFS还是什么?
加速器

您在这里所显示的只是该特定实现不起作用,并非BFS无法实现。实际上,这可能的,尽管这需要更多的工作和空间。
修剪

@Prune:这里的所有线程(我认为)都在试图证明bfs不能用于检测周期。如果您知道如何反驳证明,则应提供证明。简而言之,努力是远远不够的
Aditya Raman

由于算法是在链接的帖子中给出的,因此我认为在此处重复大纲并不恰当。
修剪

我找不到任何链接的帖子,因此要求相同。我同意您关于bfs功能的观点,并且刚刚考虑了实现。感谢您的提示:)
阿迪亚拉曼(Aditya Raman)

3

我不知道为什么我的供稿中会弹出这样一个老问题,但是以前的所有答案都是不好的,所以...

DFS可用于在有向图中查找循环,因为它可以工作

在DFS中,“访问”了每个顶点,其中访问顶点意味着:

  1. 顶点开始
  2. 从该顶点可访问的子图被访问。这包括跟踪从该顶点可到达的所有未跟踪的边,并访问所有可到达的未访问的顶点。

  3. 顶点完成。

关键特征是在完成顶点之前要跟踪从顶点可到达的所有边。这是DFS的功能,但不是BFS的功能。实际上,这就是DFS的定义。

由于此功能,我们知道在循环中的第一个顶点开始时:

  1. 循环中没有边缘被追踪。我们知道这一点,因为您只能在循环中从另一个顶点到达它们,而我们正在谈论要开始的第一个顶点。
  2. 从该顶点可到达的所有未跟踪边缘将在完成之前被跟踪,并且包括循环中的所有边缘,因为尚未跟踪到它们。因此,如果有一个循环,我们将在开始后但完成之前找到回到第一个顶点的边。和
  3. 由于从每个开始但未完成的顶点都可以访问所有被跟踪的边,因此找到该顶点的边总是表示一个周期。

因此,如果存在一个循环,则可以保证找到开始但未完成的顶点(2)的边,如果找到这样的边,则可以保证存在一个循环(3)。

这就是为什么使用DFS在有向图中查找循环的原因。

BFS不提供此类保证,因此它行不通。(尽管包含BFS或类似的子过程的循环查找算法非常好)

另一方面,无向图在任何一对顶点之间有两条路径时(即,它不是树时)都有一个循环。这在BFS或DFS期间都很容易检测到-跟踪到新顶点的边形成一棵树,其他任何边都表示一个周期。


的确,这是这里(最可能是唯一)相关的答案,详细说明了实际原因。
plasmacel

2

如果您将循环放置在树的随机位置上,则DFS会在覆盖大约一半树时,在一半时间已经遍历循环所在位置的情况下击中该循环,而在一半时间内则不会(并平均在树的其余部分中找到它),因此它的平均评估值约为树的0.5 * 0.5 + 0.5 * 0.75 = 0.625。

如果将循环放置在树中的任意位置,则BFS仅在评估该深度处的树的层时才趋向于击中循环。因此,您通常最终不得不评估平衡二叉树的叶子,这通常会导致评估更多的树。特别是,至少有两个链接中的3/4出现在树的叶子中,在这种情况下,您必须平均评估树的3/4(如果有一个链接)或7 /树的8个(如果有两个),因此您已经期望搜索1/2 * 3/4 + 1/4 * 7/8 =(7 + 12)/ 32 = 21/32 = 0.656 ...棵树,甚至没有增加从叶节点开始增加一个循环的搜索树的成本。

此外,DFS比BFS更易于实现。因此,除非您对周期有所了解(例如,周期很可能位于您搜索的根附近,BFS会给您带来好处),否则它将是一种使用方法。


那里有很多魔术数字。我不同意“ DFS更快”的说法。它完全取决于输入,在这种情况下,没有哪个输入比其他输入更普遍。
IVlad 2010年

@Vlad-数字不是魔术。它们是均值,以这种方式陈述,并且在我给出的假设条件下进行计算几乎是微不足道的。如果用均值近似是一个不好的近似,那将是有效的批评。(我明确指出,如果您可以对结构进行假设,答案可能会有所变化。)
Rex Kerr,2010年

数字很​​神奇,因为它们没有任何意义。您以DFS做得更好的情况为例,并将这些结果推断为一般情况。您的陈述是没有根据的:“当DFS被大约一半的树覆盖时,它将趋向于达到周期”:证明这一点。更不用说您不能谈论树中的循环。根据定义,树没有循环。我只是看不到你的意思。DFS会以一种方式直到陷入僵局,因此您无法知道平均会探索多少GRAPH(NOT树)。您只是选择了一个随机案例,证明没有任何依据。
IVlad

@Vlad-所有非循环完全连接的无向图是(无根无向)树。我的意思是“除了一个虚假链接之外,这将是一棵树的图形”。也许这不是该算法的主要应用-也许您想在一些缠结图中找到循环,这些缠结图中有很多链接而不是一棵树。但是,如果它是树状的,并且在所有图形上平均,则任何节点都同样有可能成为所述虚假链接的来源,这将使链接被击中时预期的树覆盖率为50%。因此,我接受该示例可能不具有代表性。但是数学应该是微不足道的。
雷克斯·克尔

1

要证明图是循环的,您只需要证明它有一个循环(边直接或间接指向自身)即可。

在DFS中,我们一次获取一个顶点,并检查它是否具有循环。一旦找到一个循环,我们就可以省略其他顶点的检查。

在BFS中,我们需要同时跟踪许多顶点边缘,而不是在发现循环周期结束时经常跟踪。随着图的大小增长,与DFS相比,BFS需要更多的空间,计算量和时间。


0

这取决于您是在谈论递归还是迭代实现。

递归-DFS访问每个节点两次。迭代BFS访问每个节点一次。

如果要检测周期,则需要在添加邻接之前和之后调查节点-既在节点上“启动”时,又在节点上“完成”时。

这需要在Iterative-BFS中进行更多工作,因此大多数人选择Recursive-DFS。

请注意,带有std :: stack的Iterative-DFS的简单实现与Iterative-BFS有相同的问题。在这种情况下,您需要将虚拟元素放入堆栈中,以跟踪在节点上“完成”工作的时间。

请参阅以下答案,以获取有关Iterative-DFS如何需要额外工作来确定何时“完成”节点(在TopoSort上下文中回答)的更多详细信息:

使用DFS进行拓扑排序而无需递归

希望这可以解释为什么人们喜欢递归-DFS来解决需要确定何时“完成”处理节点的问题。


这是完全错误的,因为使用递归还是通过迭代消除递归都没有关系。您可以实现一个访问每个节点两次的迭代DFS,就像您可以实现一个仅访问每个节点一次的递归变体一样。
plasmacel

0

BFS当您想在有向图中找到包含给定节点的最短周期时,必须使用。

例如:在此处输入图片说明

如果给定节点为2,则在三个循环中,它是- [2,3,4][2,3,4,5,6,7,8,9]&的一部分[2,5,6,7,8,9]。最短的是[2,3,4]

为了使用BFS实施此操作,必须使用适当的数据结构显式维护访问的节点的历史记录。

但是出于其他所有目的(例如:找到任何循环路径或检查循环是否存在),DFS由于其他人提到的原因,这是显而易见的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.