到目前为止,您已经获得了一些不错的答案,但是我将再添加一个采用不同方法的答案。
首先,我写了许多关于简单递归算法的文章,您可能会觉得有趣。看到
http://ericlippert.com/tag/recursion/
http://blogs.msdn.com/b/ericlippert/archive/tags/recursion/
这些是从上到下的顺序,因此请从底部开始。
其次,到目前为止,所有答案都通过考虑函数激活来描述递归语义。每个调用都进行一个新的激活,并且递归调用在此激活的上下文中执行。这是思考的一种好方法,但是还有另一种等效的方法:智能文本替换查找。
让我将您的函数重写为稍微紧凑的形式;不要认为这是任何一种特定的语言。
s = (a, b) => a > b ? 0 : a + s(a + 1, b)
我希望这是有道理的。如果您不熟悉条件运算符,则它的形式condition ? consequence : alternative
会很清楚。
现在,我们希望评估s(2,5)
做的文本替换为函数体调用的,我们这样做,然后替换a
用2
,并b
用5
:
s(2, 5)
---> 2 > 5 ? 0 : 2 + s(2 + 1, 5)
现在评估条件。我们从文字上替换2 > 5
为false
。
---> false ? 0 : 2 + s(2 + 1, 5)
现在,在文本上将所有错误条件替换为替代条件,并将所有真实条件替换为结果。我们只有错误的条件语句,因此我们在文本上用另一种替换该表达式:
---> 2 + s(2 + 1, 5)
现在,为免我不得不键入所有这些+
符号,请用文本将其常数替换为常量算术。(这有点作弊,但我不想跟踪所有括号!)
---> 2 + s(3, 5)
现在搜索并替换,这次3
是为b a
和5
b 进行调用的主体。我们将替换呼叫放在括号中:
---> 2 + (3 > 5 ? 0 : 3 + s(3 + 1, 5))
现在我们继续执行相同的文本替换步骤:
---> 2 + (false ? 0 : 3 + s(3 + 1, 5))
---> 2 + (3 + s(3 + 1, 5))
---> 2 + (3 + s(4, 5))
---> 2 + (3 + (4 > 5 ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (false ? 0 : 4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(4 + 1, 5)))
---> 2 + (3 + (4 + s(5, 5)))
---> 2 + (3 + (4 + (5 > 5 ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (false ? 0 : 5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(5 + 1, 5))))
---> 2 + (3 + (4 + (5 + s(6, 5))))
---> 2 + (3 + (4 + (5 + (6 > 5 ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + (true ? 0 : s(6 + 1, 5)))))
---> 2 + (3 + (4 + (5 + 0)))
---> 2 + (3 + (4 + 5))
---> 2 + (3 + 9)
---> 2 + 12
---> 14
我们在这里所做的只是简单的文本替换。确实,在我不得不之前,我不应该用“ 3”代替“ 2 + 1”,以此类推,但从教学上讲,它会变得很难阅读。
函数激活无非就是将函数调用替换为调用主体,并将形式参数替换为其相应的参数。您必须谨慎地聪明地引入括号,但除此之外,它仅仅是文本替换。
当然,大多数语言实际上并不将激活实现为文本替换,但是在逻辑上就是这样。
那么,无限递归又是什么呢?文本替换不会停止的递归!请注意,最终我们是如何走到无需s
再替换的步骤,然后可以将规则应用于算术运算。