递归(递推)
递归就在于反复调用自身函数,但是每次把问题范围缩小,直到缩小到可以直接得到边界数据的结果,然后再在返回的路上求出对应的解。(所以,递归很适合用来实现分治的思想)
递归的逻辑中一般有两个重要概念:
- 递归边界
- 递归式
其中递归式是将原问题分解为若干个子问题的手段,而递归边界则是分解的尽头。
经典例子:求解n的阶乘
n! = 1 * 2 * 3 * 4 * ··· * n ,这个式子写成递推式就是 n! = (n-1) * n,于是就把规模为n的问题转换为求解规模为n-1的问题。如果用函数F(n)表示n!,就可以写成F(n)=F(n-1) * n (即递归式),这样规模就变小了。
那么,如果把F(n)变为F(n-1),又把F(n-1)变为F(n-2),这样一直减小规模,什么时候是尽头呢?由于0! = 1,因此不妨以F(n) = 1作为递归边界,即当规模减小至n = 0的时候开始“回头”。
个人感觉这个“回头”是一些题中的关键点。
于是,可以有一下代码:
#include<iostream>
using namespace std;
int J(int n){
if(n==0) return 1; //当到达递归边界F(0)时,返回F(0) == 1
else return J(n-1)*n; //没有到达递归边界时,使用递归式递归下去
}
// 阶乘的递归式写法 J(n)=J(n-1)*n
int main(){
cout<<J(3)<<endl;
return 0;
}
如下图:
递归过程:
- 输入n为3,然后调用J(3),即求解3的阶乘。
- 进入J(3)函数(第一层),判断 n==0 是否成立:不成立,因此返回 J(2)*3 。注意:,此时J(2)还是未知的。因此程序会接着调用J(2),等待得到J(2)的结果后再计算J(2)*3。
- 进入J(2)函数(第二层),判断 n==0 是否成立:不成立,因此返回 J(1)*2 。注意:,此时J(1)还是未知的。因此程序会接着调用J(1),等待得到J(2)的结果后再计算J(1)*2。
- 进入J(1)函数(第三层),判断 n==0 是否成立:不成立,因此返回 J(0)*1 。注意:,此时J(0)还是未知的。因此程序会接着调用J(0),等待得到J(0)的结果后再计算J(0)*1。
- 进入J(0)函数(第四层),判断 n==0 是否成立:成立,因此返回 1 。到这为止,得到了J(0)==1,且不用再向更小范围进行递归,因此向上一层(第三层,开始“回头”)返回J(0)的结果。
- 步骤 4 (第三层)由于等到了从第四层返回的J(0),计算出了J(1)=J(0) * 1 = 1,并将J(1) 的结果返回上一层(第二层)。
- 步骤 3 (第二层)由于等到了从第四层返回的J(1),计算出了J(2)=J(1) * 2 = 2,并将J(2) 的结果返回上一层(第一层)。
- 步骤 2 (第一层)由于等到了从第四层返回的J(2),计算出了J(3)=J(2) * 3 = 6,并将J(3) 的结果返回给了main函数。
递归过程结束,得到J(3) = 6并输出。
经典例子:求斐波那契额数列的第n项
F(0) = 1,F(1) = 1,F(n) = F(n-1) + F(n-2) (n>=2的数列) 。
#include<iostream>
using namespace std;
int F(int n){
if( n==1 || n==0) return 1;
else return F(n-1)+F(n-2);
}
// 斐波那契额数列的递归式写法 F(n)=F(n-1)+F(n-2)
int main(){
cout<<F(5)<<endl;
return 0;
}
与求阶乘不同的是,求斐波那契额的过程是使用递归分治法的一个例子。对于给定的正整数n来说,把求解F(n)的问题分解为求解F(n-1)与F(n-2)这两个子问题,而 F(0)=F(1)=1 是当n很小时问题的直接解决,递归式F(n)=F(n-1)+F(n-2)则是子问题的解的合并。
蓝桥杯——试题:未名湖边的烦恼
问题描述
每年冬天,北大未名湖上都是滑冰的好地方。北大体育组准备了许多冰鞋,可是人太多了,每天下午收工后,常常一双冰鞋都不剩。
每天早上,租鞋窗口都会排起长龙,假设有还鞋的m个,有需要租鞋的n个。现在的问题是,这些人有多少种排法,可以避免出现体育组没有冰鞋可租的尴尬场面。(两个同样需求的人(比如都是租鞋或都是还鞋)交换位置是同一种排法)
输入格式
两个整数,表示m和n
输出格式
一个整数,表示队伍的排法的方案数。
问题分析
还鞋为a,借鞋为b,当借鞋的人数多于还鞋的人数,显然会出现尴尬的局面。当没有人借鞋时,显然只有一种排法。这样,我们就来讨论还鞋人数大于等于借鞋人数的情况。
A:当还鞋人数为1人,借鞋人数为1人时,只有一种排法,即,站在第一个的必须要是还鞋的。
B:当还鞋人数为2人,借鞋人数为1人时,有2种排法,即aba aab
C:当还鞋人数为2人,借鞋人数为2人时,有2种排法,即abab aabb
D:当还鞋人数为3人,借鞋人数为1人时,有3种排法,即abaa aaba aaab
E:当还鞋人数为3人,借鞋人数为2人时,有5种排法,即ababa aabba abaab aabab aaabb
仔细观察,你可以发现,在E情况中后三种排法是在D情况中的最后一个加上一个借鞋的人,前两种排法是在C情况中的最后一个加上一个还鞋的人。其实这是一个递归问题,编程出来就很容易理解了。
#include<iostream>
using namespace std;
int fun(int m,int n)
{
if (m<n)
{
return 0;//当借出数量<归还时 ,满足时就结束函数并且这种方法事是不行的,返回0
}
if (n==0)
{
return 1; //借鞋子的人为零时就一定行,所以跳出,返回1
}
return fun(m-1,n)+fun(m,n-1); //fun(m-1,n)和fun(m,n-1)表示下一次借鞋子和还鞋子的数量
}
int main()
{
int n,m;
cin>>n>>m;
cout<<fun(n,m)<<endl;
return 0;
}
算法解释摘自《算法笔记》——胡凡
做一个递归函数首先写出递推式,根据递推式去写递归函数,再搞清楚递归边界不要把函数搞成死循环就行了,注意:重点还是要准确抓住递推式。
来源:CSDN
作者:一个奔跑的C
链接:https://blog.csdn.net/henu1710252658/article/details/104183204