这个递归斐波那契的大 O 时间复杂度?

Posted

技术标签:

【中文标题】这个递归斐波那契的大 O 时间复杂度?【英文标题】:Big-O time complexity for this recursive Fibonacci? 【发布时间】:2018-05-31 22:46:08 【问题描述】:

我有一个使用递归打印斐波那契数列的程序。有更好的方法,但我被要求使用递归,所以我不得不这样做。

这是程序:

#include <stdio.h>
#define TERMS 10

long fibo(int);

int main(void)
   for(int i = 1; i <= TERMS; i++) 
       printf("%ld", fibo(i));
   
   return 0;


long fibo(int n)
    if (n < 3) 
        return 1;
    
    else 
        return fibo(n - 1) + fibo(n - 2);
    

我知道这对于斐波那契数列来说确实是一种糟糕的方法,从上面可以清楚地看出这一点,因为 TERMS 超过 35,程序需要很长时间才能完成。

我已经通过this answer 并且无法理解他们是如何解决它的,但看起来像

fibo(int n) 的时间复杂度为 O(2^n)

再一次,我可能完全错了,但我只想:

这个完整程序的时间复杂度是多少,简要说明你是如何计算的?

如果您有更好的使用递归计算斐波那契的方法,也欢迎。

【问题讨论】:

我认为递归斐波那契在 O (n^6) 附近,因为直接计算表明它是 n^6 的最高项,但我无法检查。 Why is the complexity of computing the Fibonacci series 2^n and not n^2?的可能重复 你到底是从哪里得到 n^6 的?!它是指数型的,因为它需要与计算值一样长的时间(所有递归分支都以值为 1 的叶子结尾) 我认为来自比内的公式 :) @Dragonthoughts 【参考方案1】:

c(fibo(n)) = c(fibo(n - 1)) + c(fibo(n - 2)) + O(1)

请注意,复杂度遵循与序列一样的精确公式,因为所有计算分支始终以值为 1 的叶结尾,因此可以通过斐波那契数列本身的封闭公式准确计算出精确 (theta) 复杂度

但这超出了您的问题范围,我们需要注意的是,

c(fibo(n))

我们现在只需要解决由

定义的上界级数

an = 2 * an-1 (a1,2 = 1)

结果

an = 2^n

所以,你得到了你想要的 2^n 的上 O 界。

如果你运行它几次你会得到

sigma(c(fib(n))) 从 1 到 TERMS = O(2^(TERMS + 1) - 1)

这是一个简单的数学事实,这意味着在你的情况下(TERMS = 10)你得到

2^11 - 1 = 2047


至于您关于递归执行此操作的更好方法的问题...

int fib(int n, int val = 1, int prev = 0)

    if (n == 0) 
        return prev;
    
    if (n == 1) 
        return val;
    
    return fib(n - 1, val + prev, val);

这就是所谓的尾递归,需要 O(n)(实际上它可以通过一个好的编译器进行优化以实现为循环,然后也会消耗恒定的内存消耗)

【讨论】:

请重新格式化。一点都不好看,也不好理解。 我也希望它用于完整的程序,而不仅仅是 fibo(n),因为它位于另一个 for 循环中 还有别的吗?如果您在 tesla GPU 上运行此程序,您是否希望我以卡路里为单位计算功耗? 您应该阅读我要求完整程序的问题。不仅仅适用于 fibo(n)。我从来没有要求过这个 sigma(2^n) 从 1 到 n 是 2^(n+1) - 1 所以总数是 2^11 - 1【参考方案2】:

一般来说,它背后有数学,那里解释了斐波那契:https://en.wikipedia.org/wiki/Recurrence_relation

如果您不必证明它而只需要正确地写下来,您只需要考虑算法的行为方式以及某个数字的重复次数,然后您可以尝试将其推广到任何输入n

纸是你的朋友!

如果你的递归中有值为“10”的斐波那契,你基本上是在说(10 的斐波那契是 9 的斐波那契 + 8 的斐波那契)

然后你说斐波那契 9 - 它是斐波那契 8 + 斐波那契 7 等等。

你可以画图:

我认为很明显它将继续成为几乎完整的二叉树。您可以看到,对于每个级别,节点数量都翻了一番,因此对于fib(10),它将重复自身 10 次,底部几乎是 2^10,因此对于 fib(n),它将是 2^n

如何使其在递归算法中有效?好吧,您可以从图片中看到,即 fib(7) 被求解了 3 次。所以你必须记住 fib(n) 一旦你计算了它。它可以是全局变量,也可以通过递归调用传递对对象的引用。

那你不只是说“fib(n-1)和fib(n-2)”,你先看看“fib(n-1)算不算”?如果是这样,请使用计算值而不是递归。

【讨论】:

【参考方案3】:

生成斐波那契数列的递归函数生成高度为 n 的二叉树。假设我们取 n=5。那么树形结构会是这样的:

在最底层,我们最终会得到大约 2^n 个节点。因此,时间复杂度将在 O(2^n) 左右,因为递归将针对每个叶节点重复。

我们可以通过使用 memoization 的动态编程方法显着改善这一点,它基本上将重复的子问题(如示例中的 fib(2)fib(3))存储在某种查找表中。这将时间复杂度降低到O(n)

【讨论】:

以上是关于这个递归斐波那契的大 O 时间复杂度?的主要内容,如果未能解决你的问题,请参考以下文章

斐波那契数列递归的时间复杂度

类似斐波那契数列的递归

nefu 115 斐波那契的整除

斐波那契数列的实现算法

用于求解斐波那契的 Java 8 Lambda 表达式(非递归方式)

斐波那契数列