目录
[1. 递归简介](#1. 递归简介)
[2. 递归实现](#2. 递归实现)
[3. 递归与循环](#3. 递归与循环)
[4. 例题](#4. 例题)
在蓝桥杯以及各大算法竞赛中,有很多问题看似错综复杂,但如果换一种思维方式,将其"大事化小",问题就会迎刃而解。这种思维方式在代码中的具体体现,就是我们今天要探讨的核心算法------"递归"。
1. 递归简介
递归(Recursion),从字面意思上理解,就是"递去"和"归来"。在编程语言中,递归表现为一个函数在它的函数体内部直接或间接地调用自身。
通俗地讲,递归就像是你在排队时不知道自己是第几个人,于是你问前面的人他是第几个,前面的人也不知道,他又去问更前面的人......这个不断向前询问的过程就是"递";直到问到第一个人,他明确知道自己是第一名,然后把结果告诉第二个人,第二个人再告诉第三个人,依次向后传递,直到传回给你,这个传递结果的过程就是"归"。
递归算法非常适合解决那些具有"自相似性"的问题,也就是大问题可以被拆解为结构完全相同的若干个小问题。
2. 递归实现
要用代码完美地实现一个递归,通常需要牢牢把握住以下三个核心要素:
第一步:明确函数的功能。在动手写代码前,先不要去想里面的细节,而是先在脑海中或者草稿纸上定义好这个函数接收什么参数,返回什么结果,它到底要完成一件什么样的事情。
第二步:寻找递归的边界条件(递归出口)。这是递归最重要的一步!计算机的内存是有限的,函数不可能无休止地调用下去。我们必须找到一个极其简单、不需要再继续拆解就能直接得出答案的条件,让递归在这一步停下来并开始返回。如果漏写了边界条件,程序就会陷入死循环,最终导致内存爆栈(Stack Overflow)。
第三步:找出递归的推导关系(状态转移)。也就是思考如何把当前规模较大的问题,转化为规模较小、但逻辑相同的同类问题。
3. 递归与循环
很多初学者在学习递归时,经常会把它和循环(for 或者 while)拿来做比较。实际上,递归和循环在很多场景下是可以相互转换的。
-
思维方式:循环通常是一种"自底向上"的思维,从最基础的初始状态开始,一步步迭代推导到最终目标;而递归是一种"自顶向下"的思维,先从最终目标入手,层层向下拆解,直到遇到边界条件再向上回归。
-
代码风格:递归的代码往往非常简洁、优雅,高度贴合数学公式的定义,可读性很强;而复杂的循环有时候会嵌套多层,显得有些臃肿。
-
性能开销:由于每次函数调用都需要在内存中开辟栈空间来保存局部变量和返回地址,所以递归的内存开销通常比单纯的循环要大。如果没有做好优化(例如缺乏记忆化),纯暴力的递归可能会导致时间超限。但在蓝桥杯中,掌握基础递归是学习后续高级算法(如深度优先搜索 DFS、动态规划 DP)的必经之路。
4. 例题
下面我们结合两道经典的蓝桥杯真题,来看看递归是如何在实战中发挥作用的。
4.1斐波那契数列
#include <iostream>
using namespace std;
using ll = long long;
ll fib(int x)
{
if(x == 0) return 0;
if(x == 1 || x == 2) return 1;
return fib(x- 1) + fib(x - 2);
}
int main()
{
int n;
cin >> n;
cout << fib(n) << '\n';
return 0;
}
4.2数的计算
https://www.lanqiao.cn/problems/760/learning/?page=1&first_category_id=1&problem_id=760
#include <bits/stdc++.h>
using namespace std;
int f(int x)
{
int res = 1;
for(int i = 1; i <= x / 2; i++)
{
res += f(i);
}
return res;
}
int main()
{
int n;
cin >> n;
cout << f(n) << endl;
return 0;
}
本章完。