提示:
递推与递归是算法设计的基础,难到floyed,简单到快排,都可以用递归和递推来进行设计。
一、递推是什么?
递推就是通过一层一层的推理,来推得最后结果的算法。
可能在奥数上,这只能解决小部分数据小的问题。
但是——
计算机比人脑更快,更不嫌烦!
于是超级有用的动态规划就有效的利用了这个思想。
写不出状态转移方程的话还是暴搜吧
二、递归是什么?
让我们先知道递归这种结构——
请翻开《信息学奥赛一本通(C++版)》第127页,我们便有了个定义:
调用自身的函数称为递归函数。
那么我们就可以利用递归,将大问题逐渐分成几个小问题再合并来求解。
顺便一提,利用递归,我们就可以巧(zuo)妙(si)地设计一个死循环:
#include<bits/stdc++.h>
using namespace std;
int main(){
main();
}
从这个例子我们也可以得到一个结论:
主程序也可以递归。
三、常见例题
1.数塔问题(递推,搬运自《信息学奥赛一本通(C++版)》第211页)
P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
其实在洛谷上这是一道dp题,并且在《信息学奥赛一本通(C++版)》的基础算法的第9章动态规划中的确又提到了这个问题(但是换了更典型的样例),但是——
dp中的状态转移方程不就是递推式吗?
于是呢就让我们来打个表(就是手动模拟思路,从表中寻找方程的意思):
我们令a[i][j]来存储金字塔中第i行第j个数,就可以这么存:
a | 1 | 2 | 3 | 4 | 5 |
1 | 7 | ||||
2 | 3 | 8 | |||
3 | 8 | 1 | 0 | ||
4 | 2 | 7 | 4 | 4 | |
5 | 4 | 5 | 2 | 6 | 5 |
我们就可以把金字塔存下来了,我们再令f[i][j]为加至第i行第j个所得到的最大值,于是又有下表:
f | 1 | 2 | 3 | 4 | 5 |
1 | 7=a[1][1]+max(f[0][0],f[0][1]) | ||||
2 | 10=a[2][1]+max(f[1][0],f[1][1]) | 15=a[2][2]+max(f[1][1],f[1][2]) | |||
3 | 18=a[3][1]+max(f[2][0],f[2][1]) | 16=a[3][2]+max(f[2][1],f[2][2]) | 15=a[3][3]+max(f[2][2],f[2][3]) | ||
4 | 20=a[4][1]+max(f[3][0],f[3][1]) | 25=a[4][2]+max(f[3][1],f[3][2]) | 20=a[4][3]+max(f[4][2],f[4][3]) | 19=a[4][4]+max(f[3][3],f[3][4]) | |
5 | 24=a[5][1]+max(f[4][0],f[4][1]) | 30=a[5][2]+max(f[4][1],f[4][2]) | 27=a[5][3]+max(f[4][2],f[4][3]) | 26=a[5][4]+max(f[4][3],f[4][4]) | 24=a[5][5]+max(f[4][4],f[4][5]) |
由上表我们可以知道几个接下来程序设计的重要信息:
1.状态转移方程:
f[i][j]=a[i][j]+max(f[i-1][j-1],f[i-1][j])
1<=i<=r
1<=j<=i
2.边界值(下两种边界均可行):
(1)f[1][1]=a[1][1]
(2)对于1<=i<=r,1<=j<=i时,有f[i][j]=0
3.答案为f[r]中的最大值
于是AC代码如下(1号边界自己想,我就不写了):
#include<bits/stdc++.h>
#define max(a,b)(a>b?a:b)
using namespace std;
int r,a[1001][1001],f[1001][1001],ans;
int main(){
scanf("%d",&r);
for(int i=1;i<=r;i++)for(int j=1;j<=i;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=r;i++)for(int j=1;j<=i;j++)f[i][j]=a[i][j]+max(f[i-1][j-1],f[i-1][j]);
for(int i=1;i<=r;i++)ans=max(ans,f[r][i]);
printf("%d",ans);
}
时间复杂度为O(n^2)
2.数列求和
我们又双叒叕可以打个表(但是这次程序得用递归实现):
令f(x)为加至x的最大值,于是有下表:
代码如下(示例):
0 | 1 | 2 | 3 | 4 | 5 |
0 | 1=f(0)+1 | 3=f(1)+2 | 6=f(2)+3 | 10=f(3)+4 | 15=f(4)+5 |
由上表我们可得:
f(x)=f(x-1)+x
边界:f(0)=0
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
int n;
int f(int x){
if(x==0)return 0;
return f(x-1)+x;
}
int main(){
scanf("%d",&n);
printf("%d",f(n));
}
时间复杂度为O(n)
总结
1.递推通过逐步推理推出结果;
2.递归通过自身调用求出结果。
顺便说句题外话,今后我们学到的深度优先搜索(简称深搜,字母表示为dfs)利用了深搜的思想;动态规划则利用了递推的思想。