什么是真正的迭代深度优先搜索实施建议?
What are some true Iterative Depth First Search implementation suggestions?
所以从我所看到的情况来看,我和大多数人一样认为 DFS 的迭代版本就像迭代 BFS,除了两个区别:用堆栈替换队列并将节点标记为在 POP 之后而不是在 PUSH 之后发现。
最近有两个问题很困扰我:
- 对于某些情况,它会导致不同的输出,但是我们真的有必要在我们POP之后将一个节点标记为已访问吗?为什么我们PUSH之后再做是错的?据我所知,这将导致与我们为 BFS 这样做的原因相同的结果......在我们的 queue/stack.
中没有重复项
- 现在最重要的是:我的印象是这种迭代 DFS 的实现根本不是真正的 DFS:如果我们考虑递归版本,它非常 space 高效,因为它不存储所有在一个级别上可能的邻居(就像我们在迭代版本中所做的那样),它只选择一个并与它一起去,然后它回溯并去第二个。举一个极端的例子,想象一个在中心有一个节点并连接到 100 个叶节点的图。在递归实现中,如果我们从中间节点开始,底层堆栈将增长到最大值 2 ... 一个用于中间节点,一个用于它访问的每个叶子。如果我们按照迭代版本的想法进行操作,堆栈将增长到 1000 个元素。这似乎不对。
考虑到所有这些先前的细节,我的问题是要实现真正的迭代 DFS 的方法是什么?
问题1:如果在push前check+mark,使用lessspace但是改变了访问节点的顺序。不过,你会参观一切。
问题 2:您说得对,迭代 DFS 通常会将节点的所有子节点同时放入堆栈。这会增加用于某些图表的 space,但不会改变 最坏情况 space 的用法,而且这是最简单的方法,因此通常没有理由改变那个。
偶尔你知道如果你不这样做会节省很多space,然后你可以编写一个更像递归的迭代DFS。不是将要访问的下一个节点压入堆栈,而是将父节点和其子节点列表中的一个位置或等价物压入栈中,这几乎是递归版本在递归时必须记住的内容。在伪代码中,它看起来像这样:
func DFS(start):
let visited = EMPTY_SET
let stack = EMPTY_STACK
visited.add(start)
visit(start)
stack.push( (start,0) )
while(!stack.isEmpty()):
let (parent, pos) = stack.pop()
if (pos < parent.numChildren()):
let child = parent.child[pos]
stack.push(parent,pos+1)
if (!visited.contains(child)):
visited.add(child)
visit(child)
stack.push( (child,0) )
可以看到有点复杂,入栈的记录都是元组,有些情况下很烦人。通常我们会并行使用两个堆栈而不是创建元组来推送,或者我们一次 push/pop 两条记录,具体取决于节点和子列表位置的表示方式。
所以从我所看到的情况来看,我和大多数人一样认为 DFS 的迭代版本就像迭代 BFS,除了两个区别:用堆栈替换队列并将节点标记为在 POP 之后而不是在 PUSH 之后发现。
最近有两个问题很困扰我:
- 对于某些情况,它会导致不同的输出,但是我们真的有必要在我们POP之后将一个节点标记为已访问吗?为什么我们PUSH之后再做是错的?据我所知,这将导致与我们为 BFS 这样做的原因相同的结果......在我们的 queue/stack. 中没有重复项
- 现在最重要的是:我的印象是这种迭代 DFS 的实现根本不是真正的 DFS:如果我们考虑递归版本,它非常 space 高效,因为它不存储所有在一个级别上可能的邻居(就像我们在迭代版本中所做的那样),它只选择一个并与它一起去,然后它回溯并去第二个。举一个极端的例子,想象一个在中心有一个节点并连接到 100 个叶节点的图。在递归实现中,如果我们从中间节点开始,底层堆栈将增长到最大值 2 ... 一个用于中间节点,一个用于它访问的每个叶子。如果我们按照迭代版本的想法进行操作,堆栈将增长到 1000 个元素。这似乎不对。
考虑到所有这些先前的细节,我的问题是要实现真正的迭代 DFS 的方法是什么?
问题1:如果在push前check+mark,使用lessspace但是改变了访问节点的顺序。不过,你会参观一切。
问题 2:您说得对,迭代 DFS 通常会将节点的所有子节点同时放入堆栈。这会增加用于某些图表的 space,但不会改变 最坏情况 space 的用法,而且这是最简单的方法,因此通常没有理由改变那个。
偶尔你知道如果你不这样做会节省很多space,然后你可以编写一个更像递归的迭代DFS。不是将要访问的下一个节点压入堆栈,而是将父节点和其子节点列表中的一个位置或等价物压入栈中,这几乎是递归版本在递归时必须记住的内容。在伪代码中,它看起来像这样:
func DFS(start):
let visited = EMPTY_SET
let stack = EMPTY_STACK
visited.add(start)
visit(start)
stack.push( (start,0) )
while(!stack.isEmpty()):
let (parent, pos) = stack.pop()
if (pos < parent.numChildren()):
let child = parent.child[pos]
stack.push(parent,pos+1)
if (!visited.contains(child)):
visited.add(child)
visit(child)
stack.push( (child,0) )
可以看到有点复杂,入栈的记录都是元组,有些情况下很烦人。通常我们会并行使用两个堆栈而不是创建元组来推送,或者我们一次 push/pop 两条记录,具体取决于节点和子列表位置的表示方式。