【问题标题】:DFS questions (one for rooted trees, one for graphs)DFS 问题(一个用于有根树,一个用于图形)
【发布时间】:2014-04-04 01:07:40
【问题描述】:

问题一:

对于有根树,比如二叉树,为什么人们要确保永远不会将 NULL/nil/null 压入堆栈?

例如

while(!stack.empty() ) {
    x = stack.pop();
    visit(x);

    if(x->right) stack.push(x->right);
    if(x->left) stack.push(x->left);
}

而不是

while(!stack.empty() ) {
    x = stack.pop();
    if(!x) continue;

    visit(x);
    stack.push(x->left), stack.push(x->right);
}

我的意思是,第二种形式更自然地与 preorder/DFS 的递归形式对齐,那么为什么人们通常将“check before”与 iterative 一起使用,而“check after”与 recursive 一起使用?除了节省(n + 1)个堆栈点(这不会增加空间复杂度)之外,我还有什么遗漏的原因吗?

问题2:

图上的DFS:使用迭代,为什么人们在推送当前节点的相邻节点时设置visited标志,但在递归中,我们只有在递归发生后才这样做?

例如迭代:

while(!stack.empty()){

    x=stack.pop();
    // we do not need to check if x is visited after popping

    for all u adjacent from x          
        if(!visited[u]) stack.push(u), visited[u]=true; //we check/set before push

}

但在递归中:

void DFS(Graph *G, int x)
{
    if( !visited[x] ) return; //we check/set after popping into node
    visited[x]=true;

    for all u adjacent from x
        DFS(G, u);   //we do not check if u is already visited before push
 }

所以一般来说,两个问题之间的联系是:为什么我们在将有效的东西推送到实际的堆栈对象之前更加小心(DFS 的迭代方法),但在使用硬件堆栈时却不那么小心(递归)?我错过了什么吗?硬件堆栈不是更“奢侈”吗,因为它会溢出(对于堆栈对象,我们可以将它们声明为堆外)?

感谢好心人的见解。

[ 这不仅仅是简单的编码风格,而是对算法编码方式的全面重新安排。我说的是对硬件堆栈的粗心与对软件堆栈的小心?我还想知道一些技术差异(即,它们在所有情况下是否真的是平等的方法)。几乎所有的书都遵循上述模式。 ]

【问题讨论】:

  • 你的问题只是关于代码风格。这完全是口味问题。我不认为这个问题是这里的主题。
  • 我也怀疑您声称的观察结果是否普遍正确

标签: algorithm recursion stack depth-first-search iteration


【解决方案1】:

检查何时推送或何时弹出都可以,但检查何时推送性能更好,

例如,如果您有一棵深度为 10 的二叉树,如果您检查何时弹出,则基本上是在遍历深度为 11 的树(就像您在每个叶子中添加了两个 NULL 子节点一样),即多浪费了 2048 个操作.如果它是递归的,则意味着它将进行 2048 次不必要的函数调用。

除了之前或之后检查都可以,只是碰巧你看到的代码是这样写的。

【讨论】:

    猜你喜欢
    • 2020-09-02
    • 2021-11-14
    • 1970-01-01
    • 2019-08-13
    • 2022-09-30
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多