一、什么是递归函数?
递归函数 :
函数在自己的函数体中 调用自己
递归 =
-
当前问题 拆成
-
更小规模的同类问题
-
直到一个 终止条件(base case)
少了终止条件,程序就会「无限套娃」直接栈溢出 。
二、Go 递归函数的基本结构
Go 中递归函数 和普通函数写法完全一样,没有任何特殊语法。
Go
func f(...) 返回值 {
// 1. 终止条件
if 条件 {
return 某个值
}
// 2. 递归调用
return f(更小的问题)
}
三、最经典例子:阶乘(Factorial)
数学定义
n! = n × (n-1)!
0! = 1
Go 实现
Go
func factorial(n int) int {
if n == 0 {
return 1 // 终止条件
}
return n * factorial(n-1)
}
调用
Go
fmt.Println(factorial(5)) // 120
四、递归一定要有的 2 个要素
1. 终止条件(base case)
Go
if n == 0 {
return 1
}
否则会导致栈溢出
2. 规模缩小
Go
factorial(n-1)
每次调用都 更接近终止条件
五、进阶点:斐波那契数列
定义
f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2)
Go 实现
Go
//1、声明
func fib(n int) int {
if n <= 1 {
return n
}
return fib(n-1) + fib(n-2)
}
//2、调用
fmt.Println(fib(6)) // 输出8
注意 :
这个写法在 n 大时 性能很差(重复计算)
六、递归在 Go 中的常见使用场景
1. 树 / 目录结构遍历
Go
type Node struct {
Val int
Children []*Node
}
func dfs(node *Node) {
if node == nil {
return
}
fmt.Println(node.Val)
for _, child := range node.Children {
dfs(child)
}
}
2. 文件系统递归遍历
Go
func walkDir(path string) {
files, _ := os.ReadDir(path)
for _, f := range files {
fullPath := filepath.Join(path, f.Name())
if f.IsDir() {
walkDir(fullPath)
} else {
fmt.Println(fullPath)
}
}
}
3. 分治算法
-
快速排序
-
归并排序
-
二分查找
七、递归 vs for 循环(Go 实战视角)
| 对比点 | 递归 | 循环 |
|---|---|---|
| 代码可读性 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 写起来 | 简洁 | 稍繁琐 |
| 性能 | 一般 | ⭐⭐⭐⭐ |
| 栈风险 | 有 | 无 |
Go 实战建议:
-
层级浅、逻辑清晰 用递归
-
层级深、频繁调用 用循环或显式栈
八、Go 的一个小坑:没有尾递归优化
Go 不保证尾递归优化(不像某些函数式语言)
尾递归示例(但 Go 依然会压栈)
Go
func sum(n, acc int) int {
if n == 0 {
return acc
}
return sum(n-1, acc+n)
}
即使是尾递归,
栈深度依然增长,大数据量要小心。