递归是一种分而治之进行解决问题的方法,它将问题不断地分成更小的子问题,直到子问题可以用普通的方法解决。
通常情况下,递归会使用一个不停调用自己的函数。尽管表面上看起来很普通,但是递归可以帮助我们写出非常优雅的解决方案。对于某些问题,如果不用递归,就很难解决。
我们从一个简单的问题开始学习递归。即使不用递归,我们也知道如何解决这个问题。
假设需要计算数字列表[1, 3, 5, 7, 9]的和。
下面代码展示了如何通过循环函数来计算结果。这个函数使用初始值为 0 的累加变量 sum,通过把列表中的数加到该变量中来计算所有数的和。
func sumListLoop(numList []int) int {
// 对切片中的数据进行求和:循环实现
sum := 0
for _, num := range numList {
sum += num
}
return sum
}
假设没有for循环,应该如何计算结果呢?
加法是接受两个参数(一对数)的函数。将问题从求一列数之和重新定义成求数字对之和,可以将数字列表重写成完全括号表达式,例如((((1 + 3) + 5) + 7) + 9)。
该表达式还有另一种添加括号的方式,即(1 + (3 + (5 + (7 + 9))))。
注意,最内层的括号对(7 + 9)不用循环或者其他特殊语法结构就能直接求解。事实上,可以使用下面的简化步骤来求总和。
总和 = (1 + (3 + (5 + (7 + 9))))
总和 = (1 + (3 + (5 + 16)))
总和 = (1 + (3 + 21))
总和 = (1 + 24)
总和 = 25
如何将上述想法转换成 Golang 程序呢?让我们用 Golang 切片来重新表述求和问题。数字列表 numList 的总和等于列表中的第一个元素(numList[0])加上其余元素(numList[1:])之和。可以用函数的形式来表述这个定义。
listSum(numList) = first(numList) + listSum(rest(numList))
first(numList)返回列表中的第一个元素,rest(numList)则返回其余元素。
用Golang可以轻松地实现这个等式,代码清单如下所示。
func sum(numList []int) int {
// 对切片中的数据进行求和:递归实现
if len(numList) == 1 {
return numList[0]
} else {
return numList[0] + sumListRecursion(numList[1:])
}
}
其中, 这有两个非常且必不可少的思想:
- 函数停止递归条件即已经小到足以解决问题的基本情况: if len(numList) == 1 只有一个元素的列表的和就是这个元素
- 必须改变函数状态使其向基本情况靠近,这个例子就是不断缩短列表的长度
画图理解: