关于内存使用的递归与迭代

时间:2016-10-09 20:45:52

标签: algorithm recursion iteration

假设我有一个递归和迭代解决方案(使用堆栈)来解决某些问题,例如: preorder遍历二叉树。对于当前的计算机,内存方式,对于非常大的树,在迭代版本上使用递归解决方案还是反之亦然?

我知道对于某些子问题重复的递归解决方案,如果使用递归,则会有额外的时间和内存成本。假设这不是这种情况。例如,

preOrder(Node n){
    if (n == null) return;
    print(n);
    preOrder(n.left);
    preOrder(n.right);
}

VS

preOrder(Node n){
    stack s;
    s.push(n);
    while(!s.empty()){
        Node node = s.pop();
        print(node);
        s.push(node.right);
        s.push(node.left);
    }
}

1 个答案:

答案 0 :(得分:8)

如果存在堆栈溢出的风险(在这种情况下,因为树不能保证甚至是半平衡的),那么一个健壮的程序将避免递归并使用显式堆栈。

显式堆栈可能使用更少的内存,因为堆栈帧往往比维护递归调用的上下文所必需的更大。 (例如,堆栈帧至少包含一个返回地址以及局部变量。)

但是,如果已知递归深度有限,则不必动态分配可以节省空间和时间,以及程序员时间。例如,走平衡二叉树只需要递归到树的深度,这是节点数的log 2 ;这不可能是一个非常大的数字。

正如评论员所建议的那样,一种可能的情况是树已知是正确的。在这种情况下,您可以递归左侧分支而不必担心堆栈溢出(只要您完全确定树是右倾斜的)。由于第二个递归调用位于尾部位置,因此可以将其重写为循环:

void preOrder(Node n) {
    for (; n; n = n.right) {
        print(n);
        preOrder(n.left);
        n = n.right;
}

类似的技术通常(并且应该总是)应用于快速排序:分区后,函数在较小的分区上执行,然后循环以处理较大的分区。由于较小的分区必须小于原始数组大小的一半,这将保证递归深度小于原始数组大小的log 2 ,这肯定小于50个堆栈帧,而且可能要少得多。