什么是递归?
递归(Recursion)是编程中一种强大的技术,其核心思想是:函数直接或间接地调用自身。如同俄罗斯套娃一般,每个函数调用都会解开问题的一个层级,直到达到基础条件。
递归三要素:
基准条件(Base Case):递归终止的条件
递归关系(Recursive Relation):问题分解的规律
向基准条件推进:每次递归调用必须接近基准条件
递归通过调用栈(Call Stack)实现:
递归执行原理:
每次递归调用将新状态压入栈
达到基准条件后开始出栈
依次执行后续代码(回溯阶段)
C++示例
示例1 阶乘计算
#include <iostream>
int factorial(int n) {
// 基准条件
if (n == 0 || n == 1)
return 1;
// 递归关系:n! = n * (n-1)!
return n * factorial(n - 1);
}
int main() {
std::cout << "5! = " << factorial(5) << std::endl; // 输出120
return 0;
}
输出:
factorial(5)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * 1))) = 120
示例2:斐波那契数列
#include <iostream>
int fibonacci(int n) {
if (n <= 1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int main() {
std::cout << "第7项:" << fibonacci(7) << std::endl; // 输出13
return 0;
}
示例3:目录遍历(模拟实现)
#include <iostream>
#include <vector>
struct File {
std::string name;
bool is_directory;
std::vector<File> children;
};
void traverse(const File& entry, int depth = 0) {
// 打印缩进
std::cout << std::string(depth*2, ' ');
if(entry.is_directory) {
std::cout << "[DIR] " << entry.name << std::endl;
for(const auto& child : entry.children) {
traverse(child, depth+1);
}
} else {
std::cout << entry.name << std::endl;
}
}
int main() {
File root = {"Root", true, {
{"Document", true, {
{"resume.doc", false, {}},
{"photo.jpg", false, {}}
}},
{"Program", true, {
{"main.cpp", false, {}},
{"README.md", false, {}}
}}
}};
traverse(root);
return 0;
}
输出
递归的注意事项
栈溢出风险:
深度递归可能导致栈溢出
解决方法:改用迭代或尾递归优化
重复计算问题:
斐波那契示例中的低效计算
解决方法:记忆化(Memoization)
性能考量:
递归的时间/空间复杂度通常较高
比较:迭代 vs 递归
调试技巧:
使用调试器观察调用栈
打印递归深度和参数值
何时使用递归?
✅ 适合场景:
问题具有自然递归结构(树形结构、分治算法)
代码可读性优先的场景
问题规模可预测且深度可控
❌ 避免场景:
性能关键路径
递归深度可能很大的情况
需要频繁调用的基础功能
压栈和出栈示例
#include<iostream>
void countdown(int n);
int main()
{
countdown(4);
return 0;
}
void countdown(int n)
{
std::cout<<"Counting down ...." <<n<< std::endl;
if (n > 0)
{
countdown(n - 1);
}
std::cout << n << ":Kaboom!\n" << std::endl;
}
输出:
递归调用阶段(压栈)
递归返回阶段(弹栈)
关键特点
栈帧内容:
每个栈帧保存:
函数参数(如 n 的值)
返回地址(调用后的代码位置)
局部变量(若有)
后进先出(LIFO):
最后调用的 countdown(0) 最先完成
先调用的 countdown(4) 最后完成
空间复杂度:
递归深度为 O(n),本例中栈空间占用与 n=4 成正比
栈溢出风险:
若递归深度过大(如 n=100000),会导致栈溢出(Stack Overflow)
为什么 “Kaboom!” 是逆序输出的?
因为递归的 回溯阶段(Unwinding) 遵循后进先出原则:
最后调用的 countdown(0) 最先执行 Kaboom! 输出
最早调用的 countdown(4) 最后执行 Kaboom! 输出
通过这种栈内存变化模型,可以清晰理解递归的 双向特性:
递:不断分解问题(压栈)
归:组合子问题的解(弹栈)