DFS问题(一个用于有根树,一个用于图表)

时间:2014-03-03 05:08:40

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

问题1:

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

e.g。

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);
}

我的意思是,第二种形式更自然地与预订/ DFS的递归形式对齐,那么为什么人们通常使用迭代的“检查前”,并且使用递归“检查”?除了保存(n + 1)堆栈点(不会增加空间复杂度)之外,我缺少任何理由?

问题2:

DFS on Graph:使用迭代,为什么人们在推送当前节点的相邻节点时设置了访问标志,但是在递归时,我们只在递归发生后才这样做?

e.g。迭代:

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的迭代方法)之前我们会更加小心,但在使用硬件堆栈时(递归)则不那么小心?我错过了什么吗?硬件堆栈不是更“奢侈”,因为它可以溢出(对于堆栈对象,我们可以将它们从堆中声明)?

感谢有识之士的见解。

[这不仅仅是编码风格,而是关于算法编码方式的总重排。我说的是对硬件堆栈的粗心大意而不是小心软件堆栈?我也想知道一些技术差异(即,它们对于所有情况都是真正平等的方法)。几乎所有书都遵循上述模式。 ]

1 个答案:

答案 0 :(得分:1)

检查推送时间或弹出时间均正常,但检查推送效果何时更好,

例如,如果你有一个深度为10的二叉树,如果你检查弹出的时间,你基本上就是遍历一棵深度为11的树(就像你为每一片叶子添加了两个以上的更多的孩子),那就是浪费了2048多个操作。如果它是递归的,则意味着它将使2048更多的必要函数调用。

除了可以在之前或之后检查之外,您看到的代码就是这样写的。