DFS主要用于在图形而非BFS中查找循环。有什么原因吗?两者都可以查找遍历树/图时是否已经访问过节点。
Answers:
如果图形是无向的,则BFS可能是合理的(我的客人在使用BFS展示一种有效的算法,该算法将在有向图中报告周期!),其中每个“交叉边”都定义了一个周期。如果交叉边缘为{v1, v2}
,并且包含这些节点的根(在BFS树中)为r
,则循环为r ~ v1 - v2 ~ r
(~
是路径,-
单个边缘),可以像在DFS中一样容易地报告。
使用BFS的唯一原因是,如果您知道您的(无向)图将具有较长的路径和较小的路径覆盖率(换句话说,深而窄)。在这种情况下,BFS队列所需的内存将比DFS堆栈所需的内存成比例地减少(当然两者仍然是线性的)。
在所有其他情况下,DFS显然是赢家。它适用于有向图和无向图,并且报告周期很简单-只要连接任何后缘到祖先到后代的路径,就可以得到周期。总而言之,此问题比BFS更好,更实用。
BFS不会在寻找周期中使用有向图。将A-> B和A-> C-> B视为图中从A到B的路径。BFS会说,沿着B被访问的路径之一。当继续沿下一条路径行驶时,它将说再次找到了标记的节点B,因此存在一个循环。显然这里没有周期。
我不知道为什么我的供稿中会弹出这样一个老问题,但是以前的所有答案都是不好的,所以...
DFS可用于在有向图中查找循环,因为它可以工作。
在DFS中,“访问”了每个顶点,其中访问顶点意味着:
从该顶点可访问的子图被访问。这包括跟踪从该顶点可到达的所有未跟踪的边,并访问所有可到达的未访问的顶点。
顶点完成。
关键特征是在完成顶点之前要跟踪从顶点可到达的所有边。这是DFS的功能,但不是BFS的功能。实际上,这就是DFS的定义。
由于此功能,我们知道在循环中的第一个顶点开始时:
因此,如果存在一个循环,则可以保证找到开始但未完成的顶点(2)的边,如果找到这样的边,则可以保证存在一个循环(3)。
这就是为什么使用DFS在有向图中查找循环的原因。
BFS不提供此类保证,因此它行不通。(尽管包含BFS或类似的子过程的循环查找算法非常好)
另一方面,无向图在任何一对顶点之间有两条路径时(即,它不是树时)都有一个循环。这在BFS或DFS期间都很容易检测到-跟踪到新顶点的边形成一棵树,其他任何边都表示一个周期。
如果您将循环放置在树的随机位置上,则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访问每个节点两次。迭代BFS访问每个节点一次。
如果要检测周期,则需要在添加邻接之前和之后调查节点-既在节点上“启动”时,又在节点上“完成”时。
这需要在Iterative-BFS中进行更多工作,因此大多数人选择Recursive-DFS。
请注意,带有std :: stack的Iterative-DFS的简单实现与Iterative-BFS有相同的问题。在这种情况下,您需要将虚拟元素放入堆栈中,以跟踪在节点上“完成”工作的时间。
请参阅以下答案,以获取有关Iterative-DFS如何需要额外工作来确定何时“完成”节点(在TopoSort上下文中回答)的更多详细信息:
希望这可以解释为什么人们喜欢递归-DFS来解决需要确定何时“完成”处理节点的问题。