【问题标题】:Big O of Recursive Methods递归方法的大 O
【发布时间】:2024-11-07 09:10:01
【问题描述】:

我很难确定简单递归方法的大 O。如何计算这些方法的 big-O?

案例 1) 为方法 f 找到 big-O:

int f(int x){
    if(x<1) return 1;
    return f(x-1)+g(x);
}

int g(int x){
    if(x<2) return 1;
    return f(x-1)+g(x/2);
}

案例 2)

int test(int n){
    if(x<=2) return 1;
    return test(n-2) * test(n-2);
}

案例 3)

int T(int n){
    if(n<=1) return 1;
    return T(n/2)+T(n/2);
}

【问题讨论】:

    标签: recursion big-o


    【解决方案1】:

    案例 1

    抛开基本情况(g(1) = g(0) = 1 等),您可以将g 重写为f

    f(n) = f(n-1) + g(n) <=> g(n) = f(n)-f(n-1)
    

    我们知道g被定义为:

    g(n) = f(n-1) + g(n/2)
    

    如果我们用上面重写的形式替换g(n/2),我们得到:

    g(n) = f(n-1) + f(n/2) + f(n/2-1)
    

    这意味着我们可以重写f而不引用g,方法是将f的原始定义中的g(n)替换为上面的公式:

    f(n) = f(n-1) + f(n-1) + f(n/2) + f(n/2-1)
    

    要仔细检查这是否等效,您可以运行这个程序,它接受整数 n 作为第一个参数,并打印原始 f(n) 的结果,然后是 f(n) 的重写形式(称为f2在代码中):

    #include <stdio.h>
    
    int g(int x);
    
    int f(int x) {
        if (x < 1)
            return 1;
        return f(x-1)+g(x);
    }
    
    int g(int x) {
        if (x < 2)
            return 1;
        return f(x-1)+g(x/2);
    }
    
    int f2(int x) {
        if (x < 1)
            return 1;
        return f2(x-1)+f2(x-1)+f2(x/2)-f2(x/2-1);
    }
    
    int main(int argc, char *argv[]) {
        int n;
        sscanf(argv[1], "%d", &n);
        printf("%d\n", f(n));
        printf("%d\n", f2(n));
        return 0;
    }
    

    一些例子:

    $ ./a.out 10
    1952
    1952
    $ ./a.out 11
    3932
    3932
    $ ./a.out 12
    7923
    7923
    $ ./a.out 13
    15905
    15905
    $ ./a.out 14
    31928
    31928
    $ ./a.out 15
    63974
    63974
    

    现在,如果您想象递归树,每个节点会分成 4 个子树(f(n-1)f(n-1)f(n/2)f(n/2-1) 各有一个)。每个子树的大小是不一样的,例如,如果我们在一个子树上下降并且总是跟随两个最右边的分支中的任何一个,我们就有一个深度为log(N) 的二叉树。但是还有其他分支(如果我们始终遵循f(n-1) 路径)具有深度n,并且它两次分支到n-1。因此,我们可以说它绝对是指数级的。

    要得到确切的数字有点困难,但一个明显的上限是O(4^N) - 虽然这忽略了一些分支只有log(N) 深的事实,所以实际上它比O(4^N) 好一点。

    案例 2

    再次考虑递归树。在每一点,我们分支两次(test(n-2)test(n-2))。因为我们在每次调用时将 n 减少 2,所以树将是 O(n/2) 深,所以我们需要 O(2^(n/2)) 时间来遍历树 - 再次,指数增长。不是特别有趣。

    (旁注:如果你在这里使用记忆,这将是线性的!)。

    案例 3

    与案例 2 类似的逻辑,但这次树的深度为 log(N)(因为这是您需要将 N 除以 2 才能达到基本案例的次数),所以我们得到 2^log(N) = N。所以它是线性的。

    【讨论】: