fibonacci数列递归,动态规划,循环+递推三种方法的性能比较

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ASJBFJSB/article/details/82314393

斐波那契数列的定义
1.n==1 || n==2 A(n) = 1
2.An = A(n-1)+A(n-2)

递归法:

int fibonacci(int n){
    assert(n > 0);
    //递归出口
    if(n == 1 || n == 2){
        return 1;
    }
    return fiboancci(n-1)+fibonacci(n-2);
}

这里写图片描述

这样的递归算法时间复杂度达到了O(2^n),当数据规模到达一定程度时,是不可接受的算法。为什么时间复杂度会如此之高,对于给定的一个项数n(n>=2),每次求解都需要两次进行递归,所以时间复杂度为O(2^n)。而其中还包括很多重复计算的子问题,如求解fib(4)已经知道了fib(2),但是在计算fib(3)又一次求解了fib(2),若给定的项数n较大时,其中包括非常之多的重复子问题。如何进行优化呢?

动态规划(记忆化搜索)
动态规划正是用来解决问题当中会出现重复子问题的方法。动态规划通过记录子问题的解,来避免当下次出现相同子问题时进行重复的计算。而通过递归写法的动态规划也称为记忆化搜索,通过递归记录子问题的解,一般将解存储在数组,而后通过索引找到对应问题的解。

#include <cstdio>
#include <cstring>
#include <cassert>

const int maxn = 1000;
int dp[maxn];//全局的变量存储.data段,如果在函数中开辟会占用大量的栈空间

long long fibonacci(int n){
    assert(n > 0);//防止传入错误的数据,进行断言
    if(n == 1||n == 2){
        return 1;
    }
    //搜索是否有对应的解,没有则存储
    if(dp[n] == -1){
        dp[n] = fibonacci(n-1)+fibonacci(n-2);
    }
    return dp[n];//搜索成功
}

int main(){
    memset(dp,-1,sizeof(dp));//初始化记录解的dp数组
    int n;
    //注意fibonacci的项,太大会溢出
    scanf("%d",&n);
    printf("fib(%d) = %lld\n",n,fibonacci(n));
    return 0;
}

通过记忆化搜索的方式,只需要O(n)的时间复杂度即可计算出fibonacci数列的第n的值,相比直接递归求解时间复杂度O(2^n)得到了大大的提升,算法的性能显著提高。

动态规划(循环+递推)

#include <cstdio>
#include <cstring>
#include <cassert>

const int maxn = 1000;
int dp[maxn];
int main(){
    memset(dp,-1,sizeof(dp));
    int n;//注意输入的项数
    scanf("%d",&n);
    dp[0] = 1;
    dp[1] = 1;
    for(int i=2;i < n;++i){
        dp[i] = dp[i-1]+dp[i-2];//递推
    }
    printf("%d",dp[n-1]);
    return 0;
}

时间复杂度为O(n),在循环+递推写法的动态规划,相比递归的写法更好理解,但是其本质还是相同的。都是为了解决当问题中出现重复子问题而进行重复计算的问题。

值得注意的是,使用动态规划这种思想解决问题的前提是,一个问题当中必须有重复的子问题才能使用动态规划进行解决。

猜你喜欢

转载自blog.csdn.net/ASJBFJSB/article/details/82314393