警告:此文章为原创,转载需标明出处。
提示:纯手打,难免出错,找到了问题的可以在在评论区指出,谢谢。
叠甲叠完了,让我们开始吧
递归
1. 什么是递归
瞧这就是递 "龟",写完了!
彩色静态条
开个小玩笑啊,首先要知道什么是递归,我先给大家讲个故事:
从前有座山,山里有座庙,庙里有一个老和尚和一个小和尚,老和尚再给小和尚讲一个故事: 从前有座山,山里有座庙,庙里有一个老和尚和一个小和尚,老和尚再给小和尚讲一个故事: 从前有座山,山里有座庙,庙里有一个老和尚和一个小和尚,老和尚再给小和尚讲一个故事:...
这个故事相信大家肯定不陌生,但要知道什么是递归,还有要分析其中的道理。
那这个故事的特点是什么呢,就是在故事再次提到相同的故事,这就是递归的核心理念了。
回到编程领域,我们知道一个函数是可以调用另一个函数的,也就是 函数A->函数B,作为特例,如果函数调用了自己,就跟之前的故事一样,也就是 函数A->函数A,我们把函数调用自己的情况叫做递归。
现在我们来举个最简单的例子说明。
#include <bits/stdc++.h>
using namespace std;
int f(int x){
return x+f(x-1);
}
int main(){
cout<<f(3);
return 0;
}
通过这个程序,我们来分析一下 的值到底是什么
到这里我们会发现一个问题,每次函数都会无条件的调用自己,这种调用似乎永远不会停止,这显然不是我们想要的,所以我们需要在函数中添加一个判断语句,已决定何时停止调用自己,这个例子里,我们就把参数等于 作为终止条件。
#include <bits/stdc++.h>
using namespace std;
int f(int x){
if(x>0){
return x+f(x-1);
}else{
return 0;
}
}
int main(){
cout<<f(3);
return 0;
}
那输出为什么是 呢?
我们模拟一下程序的执行过程就明白了:
像这样用判断语句把函数终止掉,这种情况就是递归的终止条件。
那用递归如何求解 一直加到 呢?
其实很简单,把上面的 换成 就行了。
#include <bits/stdc++.h>
using namespace std;
int f(int x){
if(x>0){
return x+f(x-1);
}else{
return 0;
}
}
int main(){
cout<<f(100);
return 0;
}
那为什么结果为 呢,因为我们上次传的参数是 结果为 , 如果把参数换成 ,他就会从 一直加到 了。
可能有人会比较好奇,如果不停止递归会怎么样
好了请给出答案:
揭秘答案:选2
那为什么把判断语句去掉就会错误呢,因为程序运行时,调用函数是有代价的,要占用一片叫做“栈()”的内存空间,调用函数时,数据会保存栈里。
结束时,数据就会被取出,可想而知,如果调用很多函数都不返回,数据就没地方放了,这种错误就叫做栈溢出
但是最后要注意递归与循环不同,递归的第 件事是在第一件事结束前发生的,而循环是在第一件事结束后发生的。
2.习题讲解
题目描述
用递归的方法求 的值。
输入格式
输入 。
输出格式
输出和。
输入输出样例
输入 #1
5
输出 #1
15
说明/提示
题解:
递归最重要的有两个东西:边界和调用下一层递归。
这道题的边界和如何调用下一层递归并不难找,我们就可以很轻松地打出如下递归函数:
int sum(int x){
if(x==1) return 1;//边界
else return x+sum(x-1);//调用下一层递归
}
这题毕竟是道简单题,主函数也好打。
完整代码:
#include<bits/stdc++.h>//万能头
using namespace std;
int sum(int x){//递归函数
if(x==1) return 1;
else return x+sum(x-1);
}
int main(){
int n;
scanf("%d",&n);
printf("%d",sum(n));
return 0;
}
这道题其实还有 O(1)算法,要用到小学知识等差数列求和公式。
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
scanf("%d",&n);
printf("%d",(1+n)*n/2);//套用公式
return 0;
}
2.题目描述
阿克曼 (Ackmann) 函数 A(m,n) 中,m,n 定义域是非负整数 (m≤3,n≤10),函数值定义为:
akm(m,n)=n+1;(m=0 时 )。
akm(m,n)=akm(m-1,1);(m>0,n=0 时 )。
akm(m,n)=akm(m-1,akm(m,n-1));(m,n>0 时 )。
输入格式
m 和 n。
输出格式
函数值。
输入输出样例
输入 #1复制
2 3
输出 #1复制
9
题解:
递归调用函数则是核心,每一个答案都是从其他的答案推导过来的。
如果我们往这个函数里传入一个参数 ,这个函数会发生像这样的事情:
每一项都是由前两项推导而成,这就是递归的一种简单的使用方法。
代码
#include <bits/stdc++.h>
using namespace std;
int a, b;
int ack(int a, int b){
if (a == 0) return b + 1;
if (b == 0) return ack(a - 1, 1); // 递归边界
return ack(a - 1, ack(a, b - 1)); // 函数递归调用
}
int main(){
cin >> a >> b;
cout << ack(a, b);
}
总结
- 递归:函数自己调用自己
- 只递不归会导致程序崩溃
- 要在适当的时候终止递归
递推
要搞懂递推的话,我们先看一个问题
从前有一只快乐的小鸡,他呢有一个神奇的力量,那就是——永远都不会死。当然他的儿孙也不会死,小鸡两天长成大鸡,大鸡每一个月都生一只小鸡,这时问题来了,一年后有多少鸡。
这是你可能还是想用上述方法解,但是,不可能,因为,会超时。
那为什么会超时呢,因为递归的时间复杂度是指数级增长,那这时候我们就要考虑正解 —— 递推了。
1.什么是递推
- 就是用重复的运算来推出最终答案的算法
是不是很简单,那还不来到题练练手 题目传送门
问题描述:
给定一个小孩,让他上楼梯,他不上。
用n阶台阶,小孩一次可以上k级台阶,答案对 100003 取模
这道题不同于一般的斐波那契数列,我们需要推导(瞎猜)一下公式。
原版公式:
这种斐波那契数列可以解决一下问题
有N级的台阶,你一开始在底部,每次可以向上迈2级,1级台阶,问到达第N级台阶有多少种不同方式。
这种问题我们的第i个台阶,只能从第i-1个台阶和第i-2个台阶上走来,所以公式为上。
反过来看这道题目:
有N级的台阶,你一开始在底部,每次可以向上迈最多K级台阶,问到达第NN级台阶有多少种不同方式。
我们的第i个台阶,就可以从第i-1,i-2到i-k个台阶登陆 (i≥k),这就好办了
程序如下:
#include<bits/stdc++.h>
using namespace std;
int f[100005],n,k;
int main()
{
cin>>n>>k;
f[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=min(i,k);j++)
f[i]=(f[i]+f[i-j]);
}
cout<<f[n];
return 0;
}
提交,咦,怎么错了,等等我的取模呢?
结果当然是AC的啦
#include<bits/stdc++.h>
using namespace std;
int f[100005],n,k;
int main()
{
cin>>n>>k;
f[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=min(i,k);j++)
f[i]=(f[i]+f[i-j])%100003;
}
cout<<f[n];
return 0;
}
总之,递推就是有一些题型是让你自己找数字中的规律,进而找出递推关系式,而还有些题是告诉你了递推关系式,让你自己去地推。但不管怎样,碰到递推的题一定要首先找到递推关系式,再解答。
我写了怎么多能不能给我个赞呢!谢谢!