经典算法之动态规划(斐波那契数列)

适用场景:

子问题与原问题性质相同

子问题相互关联(区别于分治算法)

求最优解,核心是穷举

动态规划特点:

  1. 重叠子问题
  2. 状态转移方程(最关键)
  3. 最优子结构

动态规划Dynamic program解题框架

  1. base_case
  2. 穷举状态
  3. 状态转移

经典案例一:斐波那契数列

f[1] = 1
f[2] = 2
f[n] = f[n-1]+f[n-2]   // 状态转移方程

斐波那契额数列并不是一个正宗的动态规划问题,至少不是求最优解问题。

代码实现——递归法(暴力递归)

/*
    斐波那契数列
    暴力递归
*/
#include <stdio.h>
#include <stdlib.h>

int fib(int n) {
    // base_case
    if (n ==1 || n ==2) {
        return n;
    }
    // 递归调用
    return fib(n - 1) + fib(n - 2);
}

int main() {
    int n = 200;
    printf("fib(%d) = %d\n", n, fib(n));
    return 0;
} 

递归法效率太低。有大量重复性计算,时间复杂度O(2^n),耗时严重。

代码实现——递归法(自顶向下/带备忘录)

先自顶向下递归,再自底向上回溯。

/*
    斐波那契数列
    带备忘录的递归
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int helper(int *memo, int n) {
    // base_case
    if (n ==1 || n ==2) {
        return n;
    }
    // 备忘录查询
    if (memo[n] != 0) return memo[n];

    memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
    return memo[n];
}

int fib(int n) {
    // 备忘录初始化
    int *memo = new int[n + 1];
    memset(memo, 0, n+1);
    // 进行带备忘录的递归
    return helper(memo, n);
}

int main() {
    int n = 50;
    printf("fib(%d) = %d\n", n, fib(n));
    return 0;
} 

现在每隔节点就只被计算一次,那么时间复杂度为O(n)。
由于多了一个new内存分配。所以多了一个空间的开支,所以空间复杂度为O(n)。
带备忘录的递归解法是空间换时间

代码实现——迭代法(自底向上)

/*
    斐波那契数列
    带备忘录的递归
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int helper(int *memo, int n) {
    // base_case
    if (n ==1 || n ==2) {
        return n;
    }
    // 备忘录查询
    if (memo[n] != 0) return memo[n];

    memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
    return memo[n];
}

int fib(int n) {
    // 备忘录初始化
    int *memo = new int[n + 1];
    memset(memo, 0, n+1);
    // 进行带备忘录的递归
    return helper(memo, n);
}

int main() {
    int n = 50;
    printf("fib(%d) = %d\n", n, fib(n));
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/weixin_44937328/article/details/115344896
今日推荐