这是一个很难以完全权威的方式回答的问题——我认为不同的人对递归有不同的思考方式。 我对递归的思考方式是强迫自己以一种非常严谨、抽象的方式思考函数是什么。函数只是一个映射。这在原则上很简单,但在实践中很容易忘记,特别是如果您习惯于用命令式术语思考——也就是说,将程序视为指令集。
暂时忘记指令,以最抽象的形式考虑阶乘函数:
X Y
--------------------
0 ------> 1
1 ------> 1
2 ------> 2
3 ------> 6
4 ------> 24
5 ------> 120
6 ------> 720
...
暂时不用担心如何计算。想想抽象映射。现在让我们考虑如何制作这个函数的递归版本。我们需要什么?好吧,我们需要一个创建不同映射的函数——不是从[1, 2, 3, ...] 到阶乘的映射,而是从Y 的一个值到下一个值的映射。换句话说(现在使用小写):
x y
--------------------
1 ------> 1
1 ------> 2
2 ------> 6
6 ------> 24
24 ------> 120
120 ------> 720
720 ------> 5040
...
现在让我们考虑如何计算this。立即出现问题:1 第一次映射到1,第二次映射到2。所以我们知道我们将不得不编写一个特殊情况来区分这两者。但是对于其他人来说,这很简单,对吧?只需将x 乘以其在列表中的位置即可。所以这意味着对于映射的所有这些部分,我们只需要知道两件事:x,以及列表中的position:
def factorial_recurrence(x, position):
return x * position
请注意,这个函数现在有两个参数,所以它实际上与上面的函数略有不同:
x, p y
------------------------
1 0 ------> 1
1 1 ------> 2
2 2 ------> 6
6 3 ------> 24
24 4 ------> 120
120 5 ------> 720
720 6 ------> 5040
这非常清楚地显示了我们如何区分1 的两个映射。现在我们只需要想出一种方法来获取位置信息。碰巧position 与X 的值相同。所以一种简单的方法是使用循环。在这里,我们通过简单地将x 设置为1 并在1 而不是0 开始我们的循环来处理X == 0:
def factorial(X):
x = 1
for position in range(1, X + 1):
x = factorial_recurrence(x, position)
return x
现在注意这里x的值被传递到factorial_recurrence,然后结果被保存为x。
所以这里真正发生的是函数的输出被传递回函数。这是一个重大的揭示:
这就是递归!
从某种意义上说,这已经是一种递归算法。只是这里的表示是由内而外的,函数还结合了递归过程之外的position信息。要明白我的意思,请看这个:
def even_factorial(X):
x = 1
for position in range(2, X + 1, 2):
x = factorial_recurrence(factorial_recurrence(x, position - 1), position)
return x
对于X 的每个偶数值,这给出了与factorial 相同的结果。 (对于X 的奇数值,它给出了X - 1 的结果。)而且我们不必止步于此。我们可以对X 的每三个值做同样的事情(为了清楚起见,打破嵌套):
def third_factorial(X):
x = 1
for position in range(3, X + 1, 3):
x = factorial_recurrence(
factorial_recurrence(
factorial_recurrence(
x,
position - 2
),
position - 1
),
position
)
return x
现在每 4 次、每 5 次等执行相同的操作。如果你继续这个过程,那么对于任何给定的X,你最终会创建一个函数,它只会返回1,直到你通过X,然后当你通过X,你会得到X 的阶乘。
在这一点上,递归的诀窍是简单地意识到我们可以通过让factorial 调用自身来自动化将循环内翻的过程。每次调用factorial,它只是在最后一个调用中嵌套另一个factorial_recurrence——除非X为0,在这种情况下,它返回1,终止嵌套调用序列。
def factorial(X):
if X == 0:
return 1
else:
return factorial_recurrence(factorial(X - 1), X)
所以这是一种复杂的递归思维方式,但它的价值在于它非常清楚地显示了递归函数的抽象与它们在命令式代码中的具体实现之间的关系。