递归函数必须有明确的终止条件
没有终止条件的递归会无限调用,最终导致
StackOverflowException。C# 对栈深度有限制(通常约 1MB 栈空间),哪怕逻辑看似简单,一旦递归层级过深就崩溃。
写递归前先问自己:什么情况下该停止?这个判断必须放在函数开头,且不依赖后续计算。
错误写法:if (n == 0) return 1;放在最后,中间还做了
n * Factorial(n-1)—— 此时已发生一次调用,没防住越界 正确写法:先检查
n ,立刻返回,再处理递归分支对用户输入要额外校验:
if (n
阶乘是最典型的 C# 递归示例
它直观体现「问题分解为相同结构的子问题」:求
n!就是
n × (n−1)!,而
(n−1)!又可继续拆解。
public static long Factorial(int n)
{
if (n < 0) throw new ArgumentException("n must be non-negative");
if (n <= 1) return 1; // 终止条件
return n * Factorial(n - 1); // 递归调用
}注意:
long类型仅支持到
20!左右;超过需用
BigInteger。另外,
Factorial(10000)会直接栈溢出——这不是算法错,是 C# 运行时限制。
避免重复计算:斐波那契递归的坑
直接写
Fib(n) = Fib(n-1) + Fib(n-2)看似自然,但时间复杂度是
O(2^n),因为大量子问题被反复计算。 调用
Fib(5)时,
Fib(3)被算两次,
Fib(2)被算三次
Fib(45)在普通机器上就要等好几秒,
Fib(50)更久 这不是递归本身的问题,而是没做记忆化(memoization)
若坚持递归,应缓存结果:
private static readonly Dictionary<int, long> _memo = new();
public static long Fib(int n)
{
if (n < 0) throw new ArgumentException("n must be non-negative");
if (n <= 1) return n;
if (_memo.TryGetValue(n, out var value)) return value;
value = Fib(n - 1) + Fib(n - 2);
_memo[n] = value;
return value;
}递归替代方案:什么时候该用循环
所有递归都能转成迭代(用栈或队列模拟调用栈),尤其当问题天然线性(如遍历树的深度优先)、或数据量大、或需精确控制内存时,迭代更稳。
文件系统遍历:递归易因路径过深崩溃,Directory.GetFiles(path, "*", SearchOption.AllDirectories)内部是迭代实现 解析嵌套 JSON 或 XML:用栈比递归更易中断、调试和限深 尾递归优化?C# 编译器不支持自动尾调用优化(Tail Call Optimization),即使写成尾递归形式(最后一个操作是调用自身),仍会压栈
真正需要递归的场景其实不多:语法树解析、回溯算法(如八皇后)、分治(归并排序的合并逻辑)——这些结构天然递归,硬改成迭代反而难读难维护。
