算法5、动态规划入门-递归与递推写法

回溯与分治都是算法思想,递归与递推是代码的具体实现方法,先思考可用什么思想,再决定用哪种代码具体实现方法,一般而言回溯都需要使用递归,而分治有递归实现也有递推实现。

动态规划的本质是递归的“记忆化”

• 递归中计算过的子问题,动态规划用数组直接保存下来;

• 动态规划按顺序填充数组,从而避免了递归的重复计算。

(数据很大时一般递归行不通,要用递推)

回溯专题见:算法3、回溯全专题(构造子集/全排列/组合/背包)-CSDN博客

分治专题见:

算法2. 分治入门(采用递归实现)_简单的递归-CSDN博客

算法4、分治思想进阶-二维分治-CSDN博客

一、递归与递推写法

1、🌰斐波那契数列的递推写法

利用for循环从最底层往上加:

#include <iostream>
const int MOD = 10007;
const int MAXN = 10000 + 1;
int f[MAXN];
int main(){
    int n; 
    scanf("%d", &n);
    f[1] = f[2] = 1;
    for(int i = 3; i <= n; i++)
    {
        f[i] = (f[i-1] % MOD  + f[i-2] % MOD) % MOD;
    }
    printf("%d\n", f[n]);
}

2、🌰数塔的递归与递推写法

(1)递归写法-采用回溯思想/分治思想 

晴问算法

采用回溯思想:
  • 用行表示递归层数用于进入下一层递归以生成后续结果;
  • 用a[固定行][i]表示该层的元素:

   第row层有row个元素,所以加入元素到a数组时索引从1-row添加即可,

  但到达该递归层后可选的元素与上一层选择的元素的列x有关,只能选x,x+1列,  所以,应把当前选择的元素的列传递下去,且当前层用for遍历可选元素,的列索引范围应为col->col+1

  • 行到达n +1(从1开始)说明已经处理完了所有行即生成了一个路径值temp,就更新最大路径值

整体就是回溯的思想 ->  需要有外部变量记当前构造的路径,和最终结果即路径的最大值,所以回溯思想的递归函数本身并不记录也不返回结果而是在DFS,因此命名为void  traverse,而不是void maxPath.

#include <iostream>
int temp = 0;
int max = 0;
int n = 0;
const int MAXN = 105;
int a[MAXN][MAXN];
void traverse(int col, int row){//col:列,row:行
    if(row == n + 1){
        if(temp > max)
            max = temp;
            return ; 
    }
    for(int i = col; i <= col+1 && i<=row; i++){
        temp += a[row][i];
        traverse(i, row+1);//把当前选择元素的列传递下去
        temp -= a[row][i];//回溯:减去已选择的元素,重加入后一个元素
    }
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= i; j++){
            scanf("%d", &a[i][j]);
        }
    traverse(1, 1);//从1行1列开始
    printf("%d\n", max);
}
采用分治思想则是:

  设递归函数(int col, int row)返回以[row][col]为塔顶的数塔的路径最大值,(所以该递归函数可以命名为int maxPath),不需要外部变量记录当前构造的路径和路径最大值

  那么当前树塔的路径最大值=自身值+左右子数塔路径最大值中更大的那个;

  边界条件/base case:当塔顶的行到达最底层/数塔只有一层,则返回自身。

#include <iostream>
using namespace std;
int n = 0;
const int MAXN = 105;
int a[MAXN][MAXN];
int maxPath(int col, int row){
    if(row == n)
        return a[row][col];
    return a[row][col] + max( maxPath(col, row+1), maxPath(col+1, row+1) );
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= i; j++){
            scanf("%d", &a[i][j]);
        }
    printf("%d\n", maxPath(1, 1));
}

但是当数据量很大,递归函数就会超时,不能用递归写法了,因此应该改用递推写法。 

(2)递推写法--就是分治思想除递归外的另一种实现(动态规划)

晴问算法

分治思想得到:当前数塔的路径最大值=自身值+左右子数塔路径最大值中更大的那个;

现在不用递归实现,改用递推实现。

递归写法与递推写法的共性:

  • 递归函数int maxPath(row, col) 相当于 状态表示dp[row][col] ,都代表以a[row][col]为塔顶的数塔的路径最大值;
  • 递归表达式 相当于 状态转移方程 :dp[row][col]  = 自身 + 左右子数塔路径最大值中更大的那个;
  • 递归边界/base case 相当于 dp[][]的初始化,即底层数塔的元素值

区别:

 处理顺序不同,递归先处理递归表达式,最后根据递归边界结束返回,而递推先把边界加入dp数组,再处理状态转移方程,且递推要给状态转移方程套上一层自倒二层开始的自底向上算每个dp[row][col]的for循环

#include <iostream>
using namespace std;
int n = 0;
const int MAXN = 105;
int a[MAXN][MAXN];
int dp[MAXN][MAXN] = {0};
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= i; j++){
            scanf("%d", &a[i][j]);
        }
    //先把底层元素加入dp
    for(int col = 1; col <= n; col++)
        dp[n][col] = a[n][col];
    //再从倒二层开始自底向上循环算每个dp[row][col] = 自身 + max ( )
    for(int row = n-1; row >=1 ; row--)
       for(int col = 1; col <= row; col++){
           dp[row][col] = a[row][col] + max(dp[row+1][col], dp[row+1][col+1]);
       }
       printf("%d\n", dp[1][1]);
}

3、🌰上楼

晴问算法

递归写法:利用分治

        递归函数int f(int n)返回上n级台阶有多少种方案数,

        递归表达式为f(n) = f(n-1) + f(n-2),即要踏上第n级台阶时,有两个办法:从第n-1级一步上去,从第n-2级两步上去,

        递归边界/base case: n == 0 || n == 1 return 1。

那么递推写法就是:

        dp[n]代表n级台阶有多少张方案数,

        状态转移方程是:dp[n] = dp[n-1] + dp[n-2],

        初始化:dp[0] = 1, dp[1] = 1, ( dp[2] = dp[0] + dp[1])。

#include<iostream>
using namespace std;
const int MAXN = 10001;
const int MOD = 10007;
int dp[MAXN] = {0};
int main(){
    int n ;
    scanf("%d", &n);
    dp[0] = 1;
    dp[1] = 1;
    for(int i = 2; i <= n; i++){
        dp[i] = (dp[i-1] % MOD+ dp[i-2] % MOD) % MOD;
    }
    printf("%d\n", dp[n]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值