为什么创建调用 O(h) 的二叉树的递归方法的复杂性 space?

Why is the space complexity of a recursive method that creates a binary tree of calls O(h)?

鉴于此方法:

int f(int n) {
  if (n <= 0) {
     return 1;
  }
  return f(n - 1) + f(n - 1);
}

创建堆栈 call/binary 树,如下所示:

      n
     /  \
   n-1   n-1
 /   \  /   \
n-2 n-2 n-2 n-2 etc

例如。 n = 3

      3
     /  \
   2     2
  / \    / \
 1   1   1   1
/ \ / \ / \ / \
0 0 0 0 0 0 0 0 

2^(n + 1) - 1 => 15 nodes/calls。 时间复杂度为 O(2^n)

我的问题是,为什么 space complexity = O(h),h 表示树的高度,在示例中为 3?换句话说,如果每个方法调用有 1 个内存 space 用于输入变量 n,那么我们可以说对于每个方法调用,有 1 个内存 space。如果有 O(2^n) 次方法调用,那么为什么 space 不等于 O(2^n)?我的参考资料说 "only O(N) exists at any given time",这对我来说没有意义。

我认为堆栈帧代表一个方法调用、它的参数和变量,以及它的调用者的 return 地址。这可能是我困惑的根源。

重要的是要注意这不是 concurrent/parallel 算法,因此计算函数输出的步骤的最终可视化不是同时发生的。

如果我们像这样重写算法,可能会更明显:

int f(int n) {
  if (n <= 0) {
     return 1;
  }

  int temp1 = f(n - 1);
  int temp2 = f(n - 1);
  return temp1 + temp2;
}

因此对第一个 f(n - 1) 和第二个 f(n - 1) 的调用不会同时发生。

这意味着在任何给定的时间点,我们都有一个线性调用堆栈,如下所示:

      f(3)
     / 
    f(2)   
   /  
  f(1) 
 /
f(0)

此时,我们有一个大小为 4 的调用堆栈。当计算 f(0) 时,最后一个元素元素从堆栈中弹出,我们将有一个大小为 3 的调用堆栈:

      f(3)
     / 
    f(2)   
   /  
  f(1)

此时,算法计算对f(1)的第二次调用(f(1)的右子树):

      f(3)
     / 
    f(2)   
   /  
  f(1)
  \
   f(0)

我们再次拥有大小为 4 的调用堆栈。在接下来的几步中,调用堆栈将转换为:

      f(3)
     / 
    f(2)   
   /  
  f(1)

然后:

      f(3)
     / 
    f(2)   

然后:

      f(3)
     / 
    f(2)
    \
     f(1)

然后:

      f(3)
     / 
    f(2)
    \
     f(1)
    /
   f(0)

然后:

      f(3)
     / 
    f(2)
    \
     f(1)

然后:

      f(3)
     / 
    f(2)
    \
     f(1)
      \
       f(0)

这个过程一直持续到算法完成。

因此,我们可以得出结论,该算法的 space 复杂度为 O(h).