4.3 Go 递归函数学习笔记

4.3 Go 递归函数学习笔记

1. 递归基本概念 — 阶乘

递归函数是自己调用自己的函数。每个递归必须有两个要素:基例(停止条件)和递归步(向基例靠近):

package main

import "fmt"

// 阶乘:n! = n * (n-1) * (n-2) * ... * 1
func factorial(n int) int {
    if n <= 1 {   // 基例:n=0 或 n=1 时返回 1(停止递归)
        return 1
    }
    return n * factorial(n-1) // 递归步:n! = n * (n-1)!
}

func main() {
    // factorial(5) 的递归展开过程:
    // 5 * factorial(4)
    // 5 * 4 * factorial(3)
    // 5 * 4 * 3 * factorial(2)
    // 5 * 4 * 3 * 2 * factorial(1)  → 基例,返回 1
    // 5 * 4 * 3 * 2 * 1 = 120
    fact5 := factorial(5)
    fmt.Println("Factorial of 5:", fact5)

    // factorial(0):基例,直接返回 1
    fact0 := factorial(0)
    fmt.Println("Factorial of 0:", fact0)
}

执行结果:

Factorial of 5: 120
Factorial of 0: 1

要点:

  • 递归 = 函数调用自身。factorial(n) 内部调用 factorial(n-1)
  • 基例(base case):if n <= 1 { return 1 } — 递归停止条件,没有基例会无限递归导致栈溢出
  • 递归步(recursive step):return n * factorial(n-1) — 每次调用向基例靠近(n 逐渐减小到 1)
  • factorial(5) 的计算过程:5 → 4 → 3 → 2 → 1(基例返回1)→ 回溯计算 21=2 → 32=6 → 46=24 → 524=120
  • factorial(0):0 ≤ 1,直接走基例,返回 1(0! = 1 是数学定义)
  • Go 没有尾递归优化(tail call optimization),过深的递归会导致栈溢出。深度大时应改用循环或迭代

2. 多基例递归 — 斐波那契数列

斐波那契数列有两个基例(n=0 和 n=1),递归步是两个子递归之和:

package main

import "fmt"

// 斐波那契数列:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
func fibonacci(n int) int {
    if n == 0 {   // 基例1:F(0) = 0
        return 0
    }
    if n == 1 {   // 基例2:F(1) = 1
        return 1
    }
    return fibonacci(n-1) + fibonacci(n-2) // 递归步:F(n) = F(n-1) + F(n-2)
}

func main() {
    // fibonacci(7) 的计算过程:
    // F(7) = F(6) + F(5)
    // F(6) = F(5) + F(4), F(5) = F(4) + F(3)
    // ... 逐步展开到基例 F(0)=0, F(1)=1
    // 最终:F(7) = 13
    fib7 := fibonacci(7)
    fmt.Println("7th Fibonacci number:", fib7)

    // 基例验证
    fib0 := fibonacci(0) // 基例1:返回 0
    fib1 := fibonacci(1) // 基例2:返回 1
    fmt.Println("0th Fibonacci:", fib0, "1st Fibonacci:", fib1)
}

执行结果:

7th Fibonacci number: 13
0th Fibonacci: 0 1st Fibonacci: 1

要点:

  • 斐波那契有两个基例:n == 0 返回 0,n == 1 返回 1
  • 递归步调用两个子递归:fibonacci(n-1) + fibonacci(n-2)
  • 数列:0, 1, 1, 2, 3, 5, 8, 13, 21...(F(7) = 13)
  • 性能陷阱:fibonacci 的朴素递归有大量重复计算。fibonacci(7) 会计算 fibonacci(3) 多次
  • 计算复杂度是 O(2^n),n=30 就已经很慢。优化方法:
    // 使用缓存(memoization)避免重复计算
    var memo = map[int]int{0: 0, 1: 1}
    func fibonacciMemo(n int) int {
        if v, ok := memo[n]; ok { return v }
        memo[n] = fibonacciMemo(n-1) + fibonacciMemo(n-2)
        return memo[n]
    }
    
  • 或直接用循环迭代,效率更高:
    func fibonacciIter(n int) int {
        a, b := 0, 1
        for i := 0; i < n; i++ { a, b = b, a+b }
        return a
    }
    

3. 递归的副作用 — 倒计时打印

递归不仅可以用于计算返回值,还可以在递归过程中执行操作(如打印):

package main

import "fmt"

// 倒计时:打印 n 到 1,最后打印 "Blast off!"
func countdown(n int) {
    if n == 0 {              // 基例:n=0,打印结束语
        fmt.Println("Blast off!")
        return
    }
    fmt.Println(n)           // 打印当前值
    countdown(n - 1)         // 递归调用 n-1
}

func main() {
    fmt.Println("Starting countdown:")
    countdown(5)
}

执行结果:

Starting countdown:
5
4
3
2
1
Blast off!

要点:

  • 递归过程:countdown(5) → 打印 5 → countdown(4) → 打印 4 → ... → countdown(0) → 打印 "Blast off!"
  • 注意 fmt.Println(n) 在 countdown(n-1) 之前,所以打印顺序是 5→4→3→2→1(递减)
  • 如果交换顺序(先递归再打印),输出会变成 1→2→3→4→5(递增):
    func countup(n int) {
        if n == 0 { return }
        countup(n - 1)      // 先递归
        fmt.Println(n)       // 递归返回后再打印
    }
    // countup(5) 输出:1, 2, 3, 4, 5
    
  • 递归函数不一定需要返回值(countdown 没有返回值,只执行打印操作)
  • 这种"先操作后递归"vs"先递归后操作"的区别是理解递归执行顺序的关键

知识点总结

知识点 关键概念
递归基本概念 函数调用自身,必须有基例(停止条件)和递归步(向基例靠近)
阶乘递归 单基例:n<=1 返回 1,递归步:n * factorial(n-1)
多基例递归 斐波那契:两个基例 F(0)=0, F(1)=1,两个子递归之和
性能陷阱 朴素递归可能大量重复计算(fibonacci O(2^n)),应缓存或改用迭代
递归执行顺序 先操作后递归 → 递减输出;先递归后操作 → 递增输出
栈溢出风险 Go 无尾递归优化,深度过大应改用循环
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容