动态规划的递归写法和递推写法

本文介绍了动态规划的基本概念,包括其作为优化问题的解决方案,并通过斐波那契数列和数塔问题展示了动态规划的递归和递推写法。递归写法利用记忆化搜索避免重复计算,递推写法自底向上求解。动态规划解决问题的关键在于重叠子问题和最优子结构。

动态规划是一种非常精妙的算法思想,它没有固定的写法,极其灵活,常常需要具体问题具体分析

 

什么是动态规划

动态规划(Dynamic Programming,DP)是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解,需要注意的是,动态规划会将每个求解过的子问题的解记录下来,这样,当下一次又碰到同样的子问题时,就可以直接使用之前记录的结果,而不是重复计算。

一般可以使用递归递推的写法来实现动态规划,其中递归写法在此处又称作记忆化搜索

动态规划的递归写法

以斐波那契(Fibonacci)数列为例,斐波那契数列的定义为F0=1,F1=1,Fn=Fn-1+Fn-2(n>=2)

int F(int n){
	if(n==0||n==1) return 1;
	else return F(n-1)+F(n-2);
}

事实上,这个递归会有很多重复的计算,由于没有保存中间计算的结果,实际复杂度会高达O(2^n),即每次都会计算F(n-1)和F(n-2)这两个分支,基本不能承受n较大的情况。

为了避免重复计算,可以开一个一维数组dp,用以保存已经计算过的结果,其中dp[n]记录F(n)的结果,用dp[n]==-1表示F(n)当前还没有被计算过

int dp[MAXN];

然后就可以在递归当中判断dp[n]是否是-1;如果不是-1,说明已经计算过F(n),直接返回dp[n]就是结果,否则,按照递归式进行递归,代码如下:

int F(int n){
	if(n==0||
<think>我们被要求回答数字三角形问题的两种实现方式:递推记忆化搜索。 根据引用[1][2][3]提供的资料,数字三角形是一个经典的动态规划问题,问题描述如下: 给定一个由数字组成的三角形,从顶部出发,每次只能移动到下一行相邻的节点(即下一行的正下方或右下方的节点),求从顶部到底部的路径中,数字之最大的路径。 两种方法: 1. 递推(自底向上):从三角形的底部开始,逐层向上计算每个节点到底部的最大路径,最终顶部节点即为所求。 2. 记忆化搜索(自顶向下):从顶部节点开始,递归地计算每个节点到底部的最大路径,同时使用一个记忆数组存储已经计算过的节点,避免重复计算。 我们将分别介绍这两种方法的实现。 首先,假设数字三角形存储在一个二维数组`arr`中,其中第i行有i个元素(行数从1开始)。为了方便,我们通常使用二维数组,行列都从0开始存储。 方法一:递推动态规划递推公式: dp[i][j] = arr[i][j] + max(dp[i+1][j], dp[i+1][j+1]) (当i从n-2到0) 其中,dp[i][j]表示从第i行第j列节点出发,到底部的路径的最大。 我们初始化最后一行的dp值为三角形最后一行的值,然后从倒数第二行开始向上递推。 方法二:记忆化搜索 我们使用递归函数dfs(i, j)表示从(i,j)节点到底部的最大路径。 如果已经计算过dfs(i, j),则直接返回结果(存储在记忆数组rec中); 否则,递归计算: rec[i][j] = arr[i][j] + max(dfs(i+1, j), dfs(i+1, j+1)) 注意:递归的边界条件是当i为最后一行时,直接返回arr[i][j]。 下面我们分别用代码展示这两种方法(使用C++Java,根据引用中的示例选择一种语言,但用户没有指定语言,我们可以用两种语言分别展示,但为了简洁,这里选择一种,比如C++用于记忆化搜索,Java用于递推,但为了统一,我们使用C++展示两种方法?) 不过,用户要求使用中文回答,且需要生成相关问题,并且注意代码格式。 考虑到引用中提供了C++的记忆化搜索Java的递推,我们可以分别用C++展示记忆化搜索,用C++展示递推(因为引用[2]是C++记忆化搜索,引用[3]是递推但用Java,我们可以用C++写递推)。 但是,为了统一,我们这里都使用C++来实现,因为引用[2]中已经有一个C++的记忆化搜索,而递推方法在引用[3]中是Java,我们可以将其转换为C++。 注意:数字三角形的输入格式,第一行是行数n,然后n行,第i行有i个数字。 递推方法(C++): 步骤: 1. 读入n三角形数据,存储到二维数组arr中(行从1到n,列从1到行号)。 2. 初始化一个dp数组(大小arr一样),将最后一行的dp值设为三角形的最后一行。 3. 从倒数第二行开始,向上遍历,对于每一行的每个位置j,计算: dp[i][j] = arr[i][j] + max(dp[i+1][j], dp[i+1][j+1]); 4. 最终dp[1][1](或dp[0][0]如果从0开始)就是答案。 记忆化搜索(C++): 步骤: 1. 读入n三角形数据,存储到二维数组arr中。 2. 初始化一个记忆数组rec,大小arr一样,初始化为一个不可能的值(比如-1,如果三角形中可能有负数,我们可以用memset初始化为-1,因为题目中数字范围未知,但通常可以这样处理,或者用其他特殊值,但引用[2]中用了-1)。 3. 编写递归函数dfs(i, j): 如果i是最后一行(即i==n),则返回arr[i][j]。 如果rec[i][j]不等于初始值(比如-1),则返回rec[i][j]。 否则,计算rec[i][j] = arr[i][j] + max(dfs(i+1, j), dfs(i+1, j+1)),并返回rec[i][j]。 4. 调用dfs(1,1)(如果从1开始索引)或dfs(0,0)(如果从0开始索引),并输出结果。 注意:引用[2]中的代码是从第1行到第n行,且行内索引从1开始,而输入时j行有j个元素,所以内层循环j从1到i(行号)。我们在写代码时也要注意索引。 下面我们分别给出两种方法的C++代码实现(注意:为了清晰,我们假设数组索引从1开始,但C++数组默认从0开始,所以我们可以调整数组大小为n+1,然后使用1到n的索引)。 递推方法(自底向上)C++代码: ```cpp #include <iostream> #include <algorithm> using namespace std; int main() { int n; cin >> n; int arr[1005][1005] = {0}; // 初始化数组为0 int dp[1005][1005] = {0}; // 输入数字三角形 for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { cin >> arr[i][j]; } } // 初始化dp数组的最后一行 for (int j = 1; j <= n; j++) { dp[n][j] = arr[n][j]; } // 从倒数第二行开始向上递推 for (int i = n-1; i >= 1; i--) { for (int j = 1; j <= i; j++) { dp[i][j] = arr[i][j] + max(dp[i+1][j], dp[i+1][j+1]); } } cout << dp[1][1] << endl; return 0; } ``` 记忆化搜索(自顶向下)C++代码(参考引用[2]): ```cpp #include <iostream> #include <cstring> #include <algorithm> using namespace std; int n; int arr[1005][1005]; // 存储三角形 int rec[1005][1005]; // 记忆数组,记录从(i,j)出发到底部的最大 int dfs(int i, int j) { if (i == n) { return arr[i][j]; } if (rec[i][j] != -1) { return rec[i][j]; } // 计算并保存到rec[i][j] rec[i][j] = arr[i][j] + max(dfs(i+1, j), dfs(i+1, j+1)); return rec[i][j]; } int main() { memset(rec, -1, sizeof(rec)); // 初始化记忆数组为-1 cin >> n; for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { cin >> arr[i][j]; } } int ans = dfs(1, 1); cout << ans << endl; return 0; } ``` 注意:以上代码中,我们假设三角形最多1000行,所以数组大小设为1005。实际使用中,如果n很大,可以考虑动态分配数组。 现在,我们按照要求生成相关问题: §§相关问题§§ 1. 数字三角形问题中,递推记忆化搜索的时间复杂度分别是多少? 2. 数字三角形问题中,如何优化递推方法的空间复杂度? 3. 除了递推记忆化搜索,还有哪些方法可以解决数字三角形问题? 4. 如果数字三角形中的路径要求是相邻节点只能往左下或右下走,那么如果允许往正下方、左下方右下方走,该如何修改算法?</think>### 数字三角形问题的递推与记忆化搜索实现 #### 问题描述 给定一个由数字组成的三角形(如引用[1]中的示例),从顶部到底部寻找一条路径,使路径上的数字之最大。路径规则:每一步只能移动到下一行相邻节点(正下方或右下方)。 #### 1. 递推实现(自底向上) **核心思想**:从三角形底部向上逐层计算每个节点到底部的最大路径,最终顶部节点的值即为全局最优解[^1][^3]。 **递推公式**: $$ dp[i][j] = arr[i][j] + \max(dp[i+1][j],\ dp[i+1][j+1]) $$ **C++ 实现**: ```cpp #include <iostream> #include <algorithm> using namespace std; int main() { int n; cin >> n; int arr[1005][1005] = {0}, dp[1005][1005] = {0}; // 输入数字三角形 for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> arr[i][j]; // 初始化最后一行 for (int j = 1; j <= n; j++) dp[n][j] = arr[n][j]; // 自底向上递推 for (int i = n-1; i >= 1; i--) for (int j = 1; j <= i; j++) dp[i][j] = arr[i][j] + max(dp[i+1][j], dp[i+1][j+1]); cout << dp[1][1] << endl; return 0; } ``` **特点**: - 时间复杂度:$O(n^2)$ - 空间复杂度:$O(n^2)$ - 无需递归栈,避免栈溢出风险[^3] #### 2. 记忆化搜索实现(自顶向下) **核心思想**:递归计算每个节点到底部的最大路径,通过记忆数组`rec[][]`避免重复计算[^1][^2]。 **递归公式**: $$ rec[i][j] = arr[i][j] + \max(dfs(i+1,j),\ dfs(i+1,j+1)) $$ **C++ 实现**: ```cpp #include <iostream> #include <cstring> #include <algorithm> using namespace std; int n, arr[1005][1005], rec[1005][1005]; int dfs(int i, int j) { if (i == n) return arr[i][j]; // 到达最后一行 if (rec[i][j] != -1) return rec[i][j]; // 已计算则直接返回 rec[i][j] = arr[i][j] + max(dfs(i+1, j), dfs(i+1, j+1)); // 递归计算并存储 return rec[i][j]; } int main() { memset(rec, -1, sizeof(rec)); // 初始化记忆数组 cin >> n; for (int i = 1; i <= n; i++) for (int j = 1; j <= i; j++) cin >> arr[i][j]; cout << dfs(1, 1) << endl; // 从顶部开始搜索 return 0; } ``` **特点**: - 时间复杂度:$O(n^2)$ - 空间复杂度:$O(n^2)$(含递归栈) - 逻辑更符合问题描述,但递归深度大时可能栈溢出[^2] #### 关键对比 | **特性** | 递推实现 | 记忆化搜索 | |------------------|------------------------|-----------------------| | **计算方向** | 自底向上 | 自顶向下 | | **空间优化** | 可优化为$O(n)$[^1] | 需$O(n^2)$存储状态 | | **栈溢出风险** | 无 | 递归深度大时存在 | | **代码逻辑** | 顺序迭代 | 递归+记忆化 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值