【问题标题】:Time and space complexity of a "nested" recursive function“嵌套”递归函数的时间和空间复杂度
【发布时间】:2019-03-02 09:00:49
【问题描述】:

在之前的一篇cs考试介绍中有一个问题:计算函数f1的空间和时间复杂度作为n的函数,假设malloc(n)的时间复杂度是O(1),它的空间复杂度为 O(n)。

int f1(int n) {
    if(n < 3)
        return 1;

    int* arr = (int*) malloc(sizeof(int) * n);
    f1(f1(n – 3));
    free(arr);

    return n;
} 

官方的解法是:时间复杂度:O(2^(n/3)),空间复杂度:O(n^2)

我试图解决它,但我不知道如何解决,直到我在笔记本上看到一条注释:既然函数返回 n,那么我们可以将 f(f(n-3)) 视为 f(n-3 )+f(n-3) 或 2f(n-3)。在这种情况下,问题变得与这个问题非常相似:Space complexity of recursive function

我试着用这种方法解决它,我得到了正确的答案。

对于时间复杂度:

T(n)=2T(n-3)+1 , T(0)=1

T(n-3)=2T(n-3*2)+1

T(n)=2*2T(n-3*2)+2+1

T(n-3*2)=2T(n-3*3)+1

T(n)=2*2*2T(n-3*3)+2*2+2+1

...

T(n)=(2^k)T(n-3*k)+2^(k-1)+...+2^2+2+1

n-3*k=0

k=n/3

===> 2^(n/3)+...+2^2+2+1=2^(n/3)[1+(1/2)+(1/2^2) +...]=2^(n/3)*常数

因此我得到了 O(2^(n/3))

对于空间复杂度:树的深度是 n/3,每次我们做 malloc 所以我们得到 (n/3)^2 因此 O(n^2)。

我的问题:

  • 为什么我们可以将 f1(f1(n – 3)) 视为 f1(n-3)+f1(n-3) 或 2f1(n-3)?
  • 如果函数没有返回n而是改变了它,例如:return n/3而不是return n,那我们该如何解决呢?我们是否将其视为 2f1((n-3)/3)?
  • 如果我们不能总是将 f1(f1(n – 3)) 视为 f1(n-3)+f1(n-3) 或 2f1(n-3) 那么我们如何绘制递归树和我们如何使用归纳法 T(n) 编写和求解?

【问题讨论】:

    标签: recursion time-complexity big-o space-complexity


    【解决方案1】:
    • 为什么我们可以将 f1(f1(n – 3)) 视为 f1(n-3)+f1(n-3) 或 2f1(n-3)?

      因为 i) 嵌套的f1首先被求值,它的返回值被用来调用外部的f1;所以这些嵌套调用等价于:

      int result = f1(n - 3);
      f1(result);
      

      ...和ii) f1 的返回值只是它的参数(基本情况除外,但渐近无关),所以上面进一步等价于:

      f1(n - 3);
      f1(n - 3); // result = n - 3
      
    • 如果函数没有返回n而是改变了它,例如:return n/3而不是return n,那我们该如何解决呢?我们是否将其视为 2f1((n-3)/3)?

      只有外部调用受到影响。同样,使用之前的等效表达式:

      f1(n - 3); // = (n - 3) / 3
      f1((n - 3) / 3);
      

      即以 f1(n - 3) + f1((n - 3) / 3) 为例。

    • 如果我们不能总是将 f1(f1(n – 3)) 视为 f1(n-3)+f1(n-3) 或 2f1(n-3) 那么我们如何绘制递归树以及我们如何使用归纳方法 T(n) 编写和求解它?

      你总是可以像上面那样将它们分成两个单独的调用,再次记住只有第二个调用会受到返回结果的影响。如果这与n - 3 不同,那么您将需要一个递归树而不是简单的扩展。要看具体问题,不用多说。

    【讨论】:

    • 好的,所以当我们到达那行代码时,将首先评估内部(“嵌套”)函数,内部函数将开始“向下递归”,这意味着它将调用 f1(n- 3),然后当我们再次到达那行代码时,内部函数将首先被评估,再次调用 f1(n-3*2) 等等,直到我们到达基本情况,然后它将开始返回值和最后它返回的值是它以 n-3 开头的值,然后外部函数将以该值开始“向下递归”。我的直觉正确吗?
    • @TitanEilabouni 不完全是-您必须将递归路径视为二叉树-每个调用分为两个调用(内部调用和外部调用)。这就是为什么像上面的答案那样单独编写它们有助于思考过程。
    猜你喜欢
    • 2019-04-21
    • 2018-02-10
    • 2018-07-23
    • 2017-09-04
    • 1970-01-01
    • 1970-01-01
    • 2018-05-29
    • 2018-08-18
    相关资源
    最近更新 更多