递归讲解

1.概念

递归,在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。简单来说,递归表现为函数调用函数本身。

递归有“递”和“归”两个过程,用电影《盗梦空间》来比喻,一层层进入梦境可以理解为“递”,又一层层醒来可以理解成“归”。

2.代码

 

void f(参数)
{
    if(边界条件)
    {
         ......
         return;
    }
    ......
    f(参数);        
}

 

以上是递归代码的基本形式,可以看出,递归并不是永无止境地深入,而是有一个“边界条件”作为由“递”转“归”的分界点。

3.用法

运用递归时一般要从3个方面思考:

  1. 定义函数功能
  2. 确定递归边界
  3. 找出递归关系式

利用递归可以解决许多经典问题以及其他算法,比如:

  • 汉诺塔问题
  • 深度优先搜索(dfs)
  • 斐波那契数列
  • ......

但万变不离其宗,之前提到递归是“通过重复将问题分解为同类的子问题而解决问题的方法”,递归解题的关键是找到将主问题分解为子问题的方法,就是所谓“大事化小,小事化了”。

4.例题

只讲理论可能会很抽象,递归需要放到具体问题中去理解。

例题1:集合的划分

首先可以考虑边界,很显然,当k=1时,就是把n个元素全放进去,方案数为1;当n=k时,每个元素占一个盒子,方案输也是1。

除此之外,当k=0即没有盒子时,没有方案;当n<k时总会有空盒子,也没有方案。

接下来就要想递归关系式,给出思路:

有一个元素an,我们可以按an是否单占一个盒子讨论

(1)当an单占一个盒子时,这样已经有一个盒子定下来了,就只需要考虑a1—an-1去分k-1个盒子,那么就可以得到:S(n,k)=S(n-1,k-1)

(2)当an不单占一个盒子时,就有其它元素与an共占一个盒子,这时问题也可以理解成a1—an-1去分k个盒子,这时方案数为S(n-1,k),接着再把an随机放到其中一个盒子中,共用k种可能,最终S(n,k)=k*S(n-1,k)

根据加法原理,最后可以得到S(n,k)=S(n-1,k-1)+k*S(n-1,k)

奉上代码:

 

 1 #include <bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 int ss(int x,int y)
 5 {
 6     if(x<y||y==0)
 7         return 0;
 8     if(y==1||x==y)
 9         return 1;
10     return ss(x-1,y-1)+y*ss(x-1,y);
11 }
12 signed main()
13 {
14     int n,k;
15     cin>>n>>k;
16     cout<<ss(n,k);
17     return 0;
18 }

 

例题2:数的计数

听完上一题,你应该对递归有了更好的理解,这题就比较简单了。

还是先考虑边界,当n=0或1时,都只能不做任何处理,就以此作为边界。

接着考虑关系式,这题比较简单,很容易想到f(n)=f(1)+f(2)+...+f(n/2)

便可以得到代码

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int ss(int x)
 4 {
 5     int s=1;
 6     if(x==1)
 7         return 1;
 8     for(int i=1;i<=x/2;i++)
 9         s+=ss(i);
10     return s;
11 }
12 int main()
13 {
14     int n;
15     cin>>n;
16     cout<<ss(n); 
17     return 0;
18 }

然后就TLE了(

在这题中,n最大到1000,这就会导致很多重复计算,比如f(6)=f(1)+f(2)+f(3),f(7)=f(1)+f(2)+f(3),这时f(1),f(2),f(3)就都算了2遍,当数据大时就会把同一个数算很多遍,最终导致TLE

所以本题可以通过记忆化优化,就是将已经算出来的值用一个数组存起来,再次遇到时直接用,这个方法也就类似于之后的动态规划(dp)

附上AC代码

#include <bits/stdc++.h>
using namespace std;
int f[1005];//数组存值
int ss(int x)
{
    int s=1;
    if(x==1)
        return 1;
    if(f[x]!=0)//记忆化
    return f[x];
    for(int i=1;i<=x/2;i++)
        s+=ss(i);
    f[x]=s;
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    int n;
    cin>>n;
    cout<<ss(n); 
    return 0;
}

5.总结

"递归”是计算机中一个比较特有的方法思路,它在其它领域,如数学中,是很少使用的。

但递归也很重要,它与递推、dp、搜索有着许多联系,可以理解为一个基石。

运用递归时还是要想好如何将问题转化为子问题,要有序地组织思路,一步步思考。

 

posted @ 2023-07-21 10:52  wyh0721  阅读(92)  评论(0)    收藏  举报