回溯与分治都是算法思想,递归与递推是代码的具体实现方法,先思考可用什么思想,再决定用哪种代码具体实现方法,一般而言回溯都需要使用递归,而分治有递归实现也有递推实现。
动态规划的本质是递归的“记忆化”:
• 递归中计算过的子问题,动态规划用数组直接保存下来;
• 动态规划按顺序填充数组,从而避免了递归的重复计算。
(数据很大时一般递归行不通,要用递推)
回溯专题见:算法3、回溯全专题(构造子集/全排列/组合/背包)-CSDN博客
分治专题见:
算法2. 分治入门(采用递归实现)_简单的递归-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]);
}