【问题标题】:DFS vs BFS .2 differencesDFS 与 BFS .2 差异
【发布时间】:2014-03-11 15:24:46
【问题描述】:

根据维基百科,DFS和BFS的实现基本上有两个区别。

他们是:
1)DFS使用堆栈,而BFS使用队列。(这个我理解)。

2) DFS 延迟检查是否已发现顶点,直到顶点从堆栈中弹出,而不是在推送顶点之前进行此检查。

我无法理解第二个区别。我的意思是为什么 DFS 在从堆栈中删除后访问节点,而 BFS 在将节点添加到队列之前访问节点。

谢谢!

额外信息:
在上述两种算法的简单实现中,我们采用一个布尔数组(我们将其命名为visited)来跟踪哪个节点被访问或未访问。问题中提到了这个visited boolean 数组。

【问题讨论】:

  • 你在维基百科的什么地方读到的? DFS 和 BFS 是根本不同的算法,不能用仅仅两分钟的细节来描述。之前已经在这里讨论过很多次了。这是一个例子stackoverflow.com/questions/20429310/… 同样,DFS 和 BFS 是两种完全不同的算法。在 BFS 中用 LIFO 替换 FIFO 将产生一个正确再现 DFS 发现序列的算法,但它仍然不是真正的 DFS 算法。
  • 非递归实现是假的。它不是 DFS。这是维基百科文章中的一个重大错误。我在上面的链接中将非递归实现描述为“伪 DFS”。
  • AnT,你能帮我解决这个问题吗? stackoverflow.com/questions/70835523/…

标签: algorithm search graph


【解决方案1】:

这可能是我第一次听说 DFS 会延迟设置“发现”属性直到从堆栈中弹出(即使在 wikipedia 上,递归和迭代伪代码都在推送之前将当前节点标记为已发现堆栈中的孩子)。此外,如果您仅在完成处理后“发现”该节点,我认为您很容易陷入无限循环。

然而,在某些情况下,我为每个节点使用两个标志:进入时设置一组,离开节点时设置一组(通常,我将 DFS 写为递归,所以,就在递归函数的末尾)。我想我在需要以下内容时使用了类似的东西:连接图中的强连接组件或临界点(=“如果删除,该图将失去连接性的节点”)。此外,您退出节点的顺序通常用于topological sorting(拓扑排序与您完成处理节点的顺序相反)。

【讨论】:

  • 为什么在推入堆栈之前没有发现孩子的标签?
  • 在 BFS 的情况下,孩子在被推入队列之前首先被标记为已发现。
【解决方案2】:

wikipedia article 提到了两种执行 DFS 的方法:使用递归和使用堆栈。

为了完整起见,我在这里复制两个:

使用递归

procedure DFS(G,v):
   label v as discovered
   for all edges from v to w in G.adjacentEdges(v) do
      if vertex w is not labeled as discovered then
         recursively call DFS(G,w)

使用堆栈

procedure DFS-iterative(G,v):
   let S be a stack
   S.push(v)
   while S is not empty
      v ← S.pop() 
      if v is not labeled as discovered:
         label v as discovered
         for all edges from v to w in G.adjacentEdges(v) do
            S.push(w)

这里要知道的重要一点是方法调用是如何工作的。有一个底层堆栈,让我们调用T。在调用该方法之前,它的参数被压入堆栈。然后该方法再次从该堆栈中获取参数,执行其操作,并将其结果推回堆栈。然后调用方法从堆栈中取出这个结果。

例如,考虑以下 sn-p:

function caller() {
    callResult = callee(argument1, argument2);
}

就堆栈T 而言,这就是发生的情况(示意图):

// inside method caller
T.push(argument1);
T.push(argument2);
"call method callee"

// inside method callee
argument2 = T.pop();
argument1 = T.pop();
"do something"
T.push(result);
"return from method callee"

// inside method caller
callResult = T.pop();

这几乎就是第二个实现中发生的情况:显式使用堆栈。您可以将在第一个 sn-p 中调用 DFS 与将顶点压入堆栈进行比较,并将在第一个 sn-p 中对 DFS 的调用与从堆栈中弹出顶点进行比较。

从堆栈中弹出顶点v 后的第一件事就是将其标记为已发现。这相当于将其标记为已发现,作为执行DFS 的第一步。

【讨论】:

  • 您能看看我添加的额外信息吗? :)
  • @hoder 你能指出你在哪里读到第二个区别吗?这对我来说没有多大意义......
  • @hoder 好的,我现在理解混乱了,我会重写我的答案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 2017-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-11
  • 2022-01-05
相关资源
最近更新 更多