H递归函数.go

发布于:2025-06-21 ⋅ 阅读:(12) ⋅ 点赞:(0)

前言:递归函数是一种强大而又充满魅力的编程技巧。它就像是一面神奇的镜子,函数在其中能够调用自身的倒影,从而以一种简洁而优雅的方式解决许多复杂的问题。 

目录

一、递归函数是啥玩意儿

二、递归函数的优缺点

优点

缺点

三、递归函数能干啥

四、递归函数咋优化

五、递归函数经典案例 - 汉诺塔问题

总结


一、递归函数是啥玩意儿

递归函数说白了,就是函数自己调用自己。不过,咱可不能让函数这么一直调用下去没完没了,不然程序就 “跑偏” 了。所以得给它设个 “停止信号”,也就是终止条件。一到这个条件,函数就老实停下来,不再调自己啦,然后把之前算出来的结果一层一层地返回去。

举个超简单的例子,算阶乘就特别适合用递归函数。阶乘就是把一个正整数 n 和所有比它小的正整数都乘起来,像 5! = 5 × 4 × 3 × 2 × 1。咱用递归的思路想,其实就是 n! = n × (n - 1)! ,那咱就让函数自己调自己,把问题越变越小,直到变成 1! = 1,这时候就让函数停住返结果。

package main

import "fmt"

func factorial(n int) int {
    if n == 1 { // 咱的终止条件就这儿,要是 n 是 1,就返 1
        return 1
    }
    return n * factorial(n-1) // 这里函数就开始调自己啦,看不看懂?
}

func main() {
    fmt.Println(factorial(5)) // 试一试,看看会不会返 120 呢
}

看看,这代码是不是超简洁?就像把问题一层层剥开,找到最里面的核心,然后再一层层包回去,把结果算出来。

二、递归函数的优缺点

优点

  • 代码简洁 :递归函数能把复杂问题拆成小问题,代码看着就清爽多了。比如算斐波那契数列(斐波那契数列就是从 0 和 1 开始,后面的数都是前两个数的和),递归写法就很简单:

package main

import "fmt"

func fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2) // 自己调自己,把问题拆小
}

func main() {
    fmt.Println(fibonacci(10)) // 算一算第 10 个斐波那契数
}

缺点

  • 性能问题 :递归调用自己会搞出好多新的栈帧(栈帧就是函数调用时在内存里占的小窝),要是调用太多次,就会把栈给搞溢出,程序就直接 “扑街” 了。而且像斐波那契数列的简单递归写法,会算好多重复的东西,效率很低。

  • 调试困难 :递归函数调来调去,调试的时候得跟着好多层函数调用,搞起来有点麻烦。

三、递归函数能干啥

  • 树或图的遍历 :比如遍历文件系统里的目录,递归地把每个文件夹和文件都瞅一遍:

package main

import (
    "fmt"
    "path/filepath"
)

func traverseDir(path string) {
    entries, err := filepath.Glob(filepath.Join(path, "*"))
    if err != nil {
        fmt.Println("哎呀,出错了:", err)
        return
    }
    for _, entry := range entries {
        if info, err := filepath.Stat(entry); err == nil && info.IsDir() {
            fmt.Println("哇,这是个文件夹:", entry)
            traverseDir(entry) // 递归遍历子文件夹
        } else {
            fmt.Println("嘿,这是个文件:", entry)
        }
    }
}

func main() {
    traverseDir("./testdir") // 假设当前目录下有个叫 testdir 的文件夹
}
  • 深度优先搜索(DFS) :在迷宫求解、八皇后这种问题里都能用上,像是在迷宫里一直往深处走,走不通了再回来换条路。

四、递归函数咋优化

  • 记忆化 :对于斐波那契数列,可以用记忆化避免重复算,搞个 “小本本” 把算过的结果记下来:

package main

import "fmt"

func fibonacci(n int, memo map[int]int) int {
    if val, exists := memo[n]; exists { // 先瞅瞅算过没
        return val
    }
    if n <= 1 {
        memo[n] = n
    } else {
        memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
    }
    return memo[n]
}

func main() {
    memo := make(map[int]int)
    fmt.Println(fibonacci(10, memo)) // 算一算第 10 个斐波那契数
}
  • 尾递归优化 :把递归调用放在函数的最后一步,有些编译器会把它优化成循环,避免把栈搞崩。比如算阶乘的尾递归写法:

package main

import "fmt"

func factorial(n int, acc int) int {
    if n == 0 {
        return acc
    }
    return factorial(n-1, n*acc) // 尾递归调用
}

func main() {
    fmt.Println(factorial(5, 1)) // 算一算 5 的阶乘
}

五、递归函数经典案例 - 汉诺塔问题

汉诺塔是个超经典的递归问题。有三根柱子 A、B、C,A 上面有 n 个盘子,按从大到小叠着。要求把所有盘子都移到 C 上,每次只能移一个,还不能把大的盘子放在小的上面。

package main

import "fmt"

func hanoi(n int, from, aux, to string) {
    if n == 1 {
        fmt.Printf("把盘子 1 从 %s 移到 %s\n", from, to)
        return
    }
    hanoi(n-1, from, to, aux)      // 把 n-1 个盘子从 from 移到 aux
    fmt.Printf("把盘子 %d 从 %s 移到 %s\n", n, from, to) // 移最后一个盘子
    hanoi(n-1, aux, from, to)      // 把 n-1 个盘子从 aux 移到 to
}

func main() {
    hanoi(3, "A", "B", "C") // 算一算 3 个盘子咋移
}

这段代码把问题拆成把 n-1 个盘子移来移去的子问题,最后就解决了整个汉诺塔问题。

总结

递归函数就是这么个既厉害又有点小脾气的工具。宝子们要是刚入门别怕,多写写多练练,理解了它的终止条件和调用过程,就能轻松用它搞定好多复杂问题。不过写的时候得注意性能问题,要是函数调用太多,栈就不够用了,必要时就用优化技巧。相信宝子们只要肯花时间,很快就能掌握递归函数的精髓,让它变成自己编程路上的好帮手。


网站公告

今日签到

点亮在社区的每一天
去签到