递归函数初步

概念

递归函数是指在函数体内直接或间接调用自身的函数。

递归是一种将大问题分解为相同结构的小问题来求解的编程技巧,在算法设计(如分治、回溯、动态规划)和数据结构(树、图)中应用广泛。

核心思想

将问题规模逐步缩小,直到达到一个可以直接求解的“基准情形”,然后逐层返回结果。

基本原理

任何一个正确的递归函数必须包含两个关键要素:

要素说明
基准情形(Base Case)问题规模足够小时,直接返回结果,不再递归调用。是递归的终止条件。
递归情形(Recursive Case)将原问题拆解为规模更小的同种子问题,通过调用自身来求解,并将子结果组合成原问题的解。

一般形式

返回值类型 函数名(参数列表) {
    if (基准条件) {
        return 直接结果;
    } else {
        // 将问题分解,调用自身
        return 函数名(缩小规模的参数);
    }
}

示例

阶乘(n!)

#include <iostream>
using namespace std;

// 递归计算 n! = n * (n-1) * ... * 1
long long factorial(int n) {
    if (n < 0) return -1;          
    if (n == 0 || n == 1) return 1; 
    return n * factorial(n - 1);    
}

int main() {
    cout << factorial(5) << endl;  // 输出 120
    return 0;
}

斐波那契数列

// 斐波那契数列:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
long long fib(int n) {
    if (n < 0) return -1;
    if (n == 0) return 0;          
    if (n == 1) return 1;           
    return fib(n - 1) + fib(n - 2); 
}

该实现存在大量重复计算,效率极低(时间复杂度 O(2ⁿ))。实际中可使用记忆化或迭代优化。

递归的优缺点

优点缺点
代码简洁、逻辑清晰,特别适合分治、树/图遍历等问题效率通常低于迭代(函数调用开销大)
易于理解问题的分解结构容易导致栈溢出(递归深度过大时)
某些问题天然适合递归(如汉诺塔、快速排序)存在重复计算可能(如朴素斐波那契)
使代码与数学归纳思想一致占用更多内存(每层栈帧)

常见问题与注意事项

缺少基准情形 → 无限递归

int bad_factorial(int n) {
    return n * bad_factorial(n - 1);  // 最终栈溢出
}

递归深度过大 → 栈溢出

默认栈空间通常为 1~8 MB,每层栈帧几十字节到几百字节。深度超过 ~10⁵ 可能溢出。解决方法:

  • 改用迭代
  • 增大栈空间(编译器选项或 sys/resource.h
  • 尾递归(如适用)

重复计算 → 效率爆炸

斐波那契朴素递归复杂度 O(2ⁿ),应使用记忆化搜索(递归+缓存)或动态规划。

递归与迭代对比

方面递归迭代
代码长度通常更短可能较长
可读性(适合的场景)高(分治、树)
时间复杂度取决于算法,有重复计算时差通常更优
空间复杂度O(深度) 栈空间O(1) 或 O(辅助空间)
风险栈溢出无栈溢出风险
适用场景问题天然具有递归结构(树、递归定义的数据结构)线性、简单循环即可表达的问题

递归是一种强大的思维工具,而不只是编码技巧。理解递归能帮助你更好地掌握分治、搜索、动态规划等高级算法思想。