递归专题(1)
递归
学习目标:了解递归并掌握编写代码的技能。
一、知彼知己,百战不殆——递归详解
1.官方讲解
递归算法(英语:recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此在很多函数编程语言(如C++) 中习惯用递归来实现循环。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2.小栗子
一个简单的例子可以帮助理解递归——求阶乘。请看:
n!=n*(n-1)!(n>=1);
=1 (n=0);
这是求阶乘的公式。我们显然可以写一个子函数来求n!
(记这个函数是fac),可是注意到,求n!,我就要求(n-1)!。那么问题来了,求(n-1)! 所用到的函数不还是我们写的这个fac函数吗?
So,我们只要再次调用本身就ok了。下面给出子函数:
int fac(int n) { int f; if(n<o) printf("error!"); else if(n==0) f=1; else f=n*fac(n-1); return f; }
看似好像不比循环简洁,可是,当写复杂一点的代码时,递归的优点就显露出来了。
二、小试牛刀,初试身手——简单列题
例题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 }
然后就超时了
在这题中,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;
}
三、一思尚存,此志不懈——递归总结
"递归”是计算机中一个比较特有的方法思路,它在其它领域,如数学中,是很少使用的。
但递归也很重要,它与递推、dp、搜索有着许多联系,可以理解为一个基石。
运用递归时还是要想好如何将问题转化为子问题,要有序地组织思路,一步步思考。
(此文中部分内容来此他人随笔,在此附上原文链接 递归讲解 - wyh0721 - 博客园 (cnblogs.com) )
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异