家里收拾衣柜时,总喜欢把衣服一层层分类,T恤叠好放进抽屉,外套挂起来,再把当季的收进收纳箱。这种一层套一层、逐步处理的方式,其实和编程中的递归思维很像。在 Scala 里写递归函数,也是这样一步步把问题拆开,直到最简单的情况。
从一个简单的例子开始
比如想算一个数字的阶乘,5 的阶乘就是 5 × 4 × 3 × 2 × 1。这个问题可以拆成:5 × 4的阶乘,而4的阶乘又依赖3的阶乘,一直拆到1为止。这就是典型的递归场景。
def factorial(n: Int): Int = {
if (n <= 1) 1
else n * factorial(n - 1)
}
这个函数没有用循环,而是自己调用自己,直到满足终止条件。就像整理行李箱,先放大的物件,再把小件塞进缝隙,最后一层一层合上盖子。
别忘了标注返回类型
Scala 有时候能自动推断类型,但在递归函数中,最好明确写出返回值类型。否则编译器可能会报错,因为它在函数没定义完时还不知道返回什么。
def sumList(nums: List[Int]): Int = {
nums match {
case Nil => 0
case head :: tail => head + sumList(tail)
}
}
这个函数用来计算列表中所有数的和。空列表返回 0,否则取第一个数加上剩下部分的和。这种模式在处理列表、树形结构时特别常见,就像整理书架时,一本一本拿下来清点,再重新摆放。
尾递归优化让程序更轻快
如果递归层数太多,可能会导致栈溢出。好在 Scala 支持尾递归优化,只要把递归调用放在函数的最后一步,编译器就能把它转成类似循环的结构。
import scala.annotation.tailrec
def factorialTail(n: Int): Int = {
@tailrec
def loop(acc: Int, num: Int): Int = {
if (num <= 1) acc
else loop(acc * num, num - 1)
}
loop(1, n)
}
这里用了一个内部函数 loop,通过累加器 acc 保存中间结果。每次更新参数,直到结束。就像打包快递,每放一件物品就记下当前总重量,而不是等全部装完再算。
写递归函数,关键是要想清楚“最简单的情况怎么处理”和“复杂情况怎么拆解”。就像整理房间,先决定最小单位怎么归位,再一层层往外推。多练几次,自然就顺手了。