使用斐波那契数列讲解尾递归

对于特别深的递归,可能会导致栈溢出。

使用迭代式实现递归时,可以把递归深度转化为"堆内存占用"。而堆内存比栈内存大得多,几乎不会溢出。

使用斐波那契数列(Fibonacci)来讲解尾递归是最经典、最清晰的例子。

我们通过对比普通递归 和 尾递归两种写法,就能明白为什么尾递归不会爆栈。

1.普通递归

这是最符合数学定义的写法:F(n) = F(n-1) + F(n-2)

复制代码
// 计算第 n 个斐波那契数
int fib_naive(int n) {
    if (n <= 1) return n;
    // 关键点:最后一步是做加法,而不是直接返回函数调用
    return fib_naive(n - 1) + fib_naive(n - 2); 
}
它的问题在哪?

想象你在做一个任务:

  1. 为了算 fib(5),你得先算 fib(4)。但你还得记着:"等 fib(4) 算完了,我还要加上 fib(3) 呢,所以我现在不能走,得在原地等着。"
  2. 为了算 fib(4),他又得等 fib(3)...
  3. 所有人都在"原地等着"(保留栈帧),直到最底层算出结果。
  4. 这会导致栈空间 随着 n 线性增长(甚至指数级计算量),非常浪费。

2. 尾递归 (Tail Recursion)

尾递归的核心思想是:"做完这一步,我就把所有需要的东西都交给你,我就可以下班了,不用等着。"

为了实现这一点,我们需要引入 累加器(Accumulator)*来传递中间结果。

复制代码
// 辅助函数,带累加器:
// a: 当前这一项 (F_i)
// b: 下一项 (F_{i+1})
// count: 还需要算多少次
int fib_tail_helper(int n, int a, int b) {
    if (n == 0) return a;
    
    // 关键点:最后一步仅仅是单纯的函数调用,没有加法,没有其他操作!
    // "我要做的事做完了,剩下的全交给下一个人,我不必留在这里等着"
    return fib_tail_helper(n - 1, b, a + b);
}

// 接口
int fib_tail(int n) {
    return fib_tail_helper(n, 0, 1);
}
它的运行过程 (Trace):

假设我们要算 fib(5)

  1. helper(5, 0, 1): 我把接力棒交给下一棒 helper(4, 1, 1)我下班了
  2. helper(4, 1, 1): 接力棒交给 helper(3, 1, 2)我下班了
  3. helper(3, 1, 2): 接力棒交给 helper(2, 2, 3)我下班了
  4. helper(2, 2, 3): 接力棒交给 helper(1, 3, 5)我下班了
  5. helper(1, 3, 5): 接力棒交给 helper(0, 5, 8)我下班了
  6. helper(0, 5, 8): n 到了 0,直接返回结果 5

注意到了吗?在任何时刻,内存里只需要保存一个人的状态。上一轮的状态可以直接被覆盖。

3. 编译器怎么优化它? (TCO)

支持尾调用优化 (Tail Call Optimization, TCO) 的编译器(如 GCC -O2, OCaml, Scala),看到这种"最后一步纯调用"的代码,会自动把它翻译成类似 while 循环的汇编代码:

复制代码
// 编译器眼里的 fib_tail_helper 其实就是这个:
int fib_iter(int n) {
    int a = 0;
    int b = 1;
    while (n > 0) {
        int next_b = a + b;
        a = b;
        b = next_b;
        n--;
    }
    return a;
}

这就是为什么尾递归既有递归的代码美感 (无需手动管理 while 变量),又有迭代的运行效率(不占栈空间)。

总结

  1. 普通递归:回来还要干活(比如做加法),所以得留着栈帧。
  2. 尾递归:回来不用干活(直接返回结果),所以可以直接复用栈帧(Jump 过去就行)。
相关推荐
罗湖老棍子2 分钟前
A Horrible Poem(信息学奥赛一本通- P1460) [POI 2012] OKR-A Horrible Poem(洛谷-P3538)
算法·哈希·欧拉筛·错位重叠
程序员爱德华8 分钟前
LeetCode刷题
算法·leetcode
memcpy09 分钟前
LeetCode 1202. 交换字符串中的元素【无向图连通分量】中等
算法·leetcode·职场和发展
fengfuyao98511 分钟前
基于遗传算法的分布式电源选址定容优化(考虑环境因素)
算法·matlab·平面
睡觉就不困鸭12 分钟前
第10天 删除有序数组中的重复项
数据结构·算法
Chase_______16 分钟前
LeetCode 643:子数组最大平均数 I
算法·leetcode
笨笨饿26 分钟前
#65_反激电源
stm32·单片机·嵌入式硬件·算法·硬件工程·个人开发
wengqidaifeng27 分钟前
数据结构:排序(下)---进阶排序算法详解
数据结构·算法·排序算法
MicroTech202527 分钟前
突破单机量子计算限制:MLGO微算法科技的新型分布式量子算法模拟平台实现高效验证
科技·算法·量子计算
没有天赋那就反复28 分钟前
C++里面引用参数和实参的区别
开发语言·c++·算法