递归(再理解)

a 夏天 提交于 2020-02-06 04:30:03

递归(递推)

递归就在于反复调用自身函数,但是每次把问题范围缩小,直到缩小到可以直接得到边界数据的结果,然后再在返回的路上求出对应的解。(所以,递归很适合用来实现分治的思想)

递归的逻辑中一般有两个重要概念:

  1. 递归边界
  2. 递归式

其中递归式是将原问题分解为若干个子问题的手段,而递归边界则是分解的尽头。

经典例子:求解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;
}

如下图:
在这里插入图片描述
递归过程:

  1. 输入n为3,然后调用J(3),即求解3的阶乘。
  2. 进入J(3)函数(第一层),判断 n==0 是否成立:不成立,因此返回 J(2)*3 。注意:,此时J(2)还是未知的。因此程序会接着调用J(2),等待得到J(2)的结果后再计算J(2)*3。
  3. 进入J(2)函数(第二层),判断 n==0 是否成立:不成立,因此返回 J(1)*2 。注意:,此时J(1)还是未知的。因此程序会接着调用J(1),等待得到J(2)的结果后再计算J(1)*2。
  4. 进入J(1)函数(第三层),判断 n==0 是否成立:不成立,因此返回 J(0)*1 。注意:,此时J(0)还是未知的。因此程序会接着调用J(0),等待得到J(0)的结果后再计算J(0)*1。
  5. 进入J(0)函数(第四层),判断 n==0 是否成立:成立,因此返回 1 。到这为止,得到了J(0)==1,且不用再向更小范围进行递归,因此向上一层(第三层,开始“回头”)返回J(0)的结果。
  6. 步骤 4 (第三层)由于等到了从第四层返回的J(0),计算出了J(1)=J(0) * 1 = 1,并将J(1) 的结果返回上一层(第二层)。
  7. 步骤 3 (第二层)由于等到了从第四层返回的J(1),计算出了J(2)=J(1) * 2 = 2,并将J(2) 的结果返回上一层(第一层)。
  8. 步骤 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;
 }

算法解释摘自《算法笔记》——胡凡

做一个递归函数首先写出递推式,根据递推式去写递归函数,再搞清楚递归边界不要把函数搞成死循环就行了,注意:重点还是要准确抓住递推式。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!