Go 语言递归函数

递归是函数直接或间接调用自身的编程技巧,适合解决"一个问题可以拆成若干个结构相同的更小问题"的场景,比如阶乘、斐波那契、目录与树的遍历。写递归最重要的一条是必须存在终止条件:否则函数会无限调用自己,直到栈溢出程序崩溃。(说明:未给出完整 main 的片段需放入 main 函数运行)

递归的组成:终止条件 + 递归步骤

一个递归函数由两部分组成:基线条件在最小问题上直接给出答案、不再递归;递归步骤把问题规模缩小并再次调用自身。只写递归步骤、忘了基线条件是最典型的错误:

func down(n int) int {
	return down(n - 1) // 没有终止条件
}
// 运行报错:goroutine stack exceeds 1000000000-byte limit

每调用一次自身,系统就要在"调用栈"上压入一层信息,所以递归深度不是无限的。

阶乘递归完整示例

n! = n × (n-1)!,且 0! = 1,天然是递归定义。下面是完整可运行程序:

package main

import "fmt"

func factorial(n int) int {
	if n <= 1 { // 终止条件:基线
		return 1
	}
	return n * factorial(n-1) // 递归步骤:规模减 1
}

func main() {
	fmt.Println(factorial(5)) // 输出:120
	fmt.Println(factorial(3)) // 输出:6
	fmt.Println(factorial(0)) // 输出:1
}

斐波那契数列示例

斐波那契的定义 f(0)=0、f(1)=1、f(n)=f(n-1)+f(n-2),可以直接翻译成递归:

func fib(n int) int {
	if n < 2 { // 终止条件
		return n
	}
	return fib(n-1) + fib(n-2) // 拆成两个子问题
}
// 放入 main 后调用:
// fmt.Println(fib(10)) // 输出:55

注意:这种朴素写法会重复计算大量子问题(fib(40) 已明显变慢),可用"记忆化"或循环优化。

递归执行过程简讲

以 factorial(3) 为例:程序先一路"递"下去——factorial(3) 调用 factorial(2),factorial(2) 再调用 factorial(1);到达基线条件后开始逐层"归"回来——factorial(1) 返回 1,于是 factorial(2)=2×1=2,最后 factorial(3)=3×2=6。所有未返回的调用都挂在调用栈上,所以递归也叫"先递后归"。

递归 vs 循环(含尾递归)

  • 适用场景:递归代码简洁、贴近数学定义,适合树/图遍历、分治、回溯等"天然递归"的结构;循环适合简单线性迭代,性能更好且没有额外栈开销。
  • 栈开销:每层递归占一个栈帧,深度达到几十万层就会栈溢出,循环则不会。
  • 尾递归一句话:若递归调用是函数最后一个动作、结果直接返回不再参与运算,就叫尾递归,理论上可改写成循环等价形态;但 Go 目前不做尾调用优化,深层递归仍需自行改写为循环。

小结:递归是"函数调用自身",代码简洁但要牢记终止条件;它吃调用栈,深度过大时应优先改用循环或尾递归形态。

笔记加载中…