我理解递归的概念,我感到困惑的是流量控制。我发现这有两种方式,一种是我得到的,另一种是我不喜欢的。示例一:
def fact(n):
if n == 0:
return 1
else:
return n * fact(n-1)
所以在这个例子中,如果我们运行fact(3),会发生以下情况:
fact(3) = 3*fact(3-1)`
fact(2) = 2*fact(2-1)
fact(1) = 1*fact(1-1)
fact(0) = 1
或合并:3*2*1*1 = 6
现在,对于下面的内容,我被绊倒的地方就是流量控制的工作原理。我在脑海中根深蒂固,当一个函数被调用时,其他一切都被暂停,直到该函数完成,此时程序返回到main。以下是我的大脑认为发生在下面的事情:
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result
我们称之为factorial(3):
factorial(3)=factorial(2)=factorial(1)=factorial(0)=1
我认为发生这种情况的原因是因为在调用之后分配了result
,并且在我看来代码永远不会到达,因为流控制在分配result
之前暂停main。我认为这个函数只是运行n==0
的测试,直到返回1
,然后程序退出。
帮助我理解为什么我似乎无法将这个概念化。
答案 0 :(得分:6)
以下是该计划流程的概述。看起来可能有点令人困惑,但它可能会有所帮助。这里,不同的选项卡级别表示不同的堆栈,每一行是程序执行的命令。
factorial(3)
| factorial(2)
| | factorial(1)
| | | factorial(0)
| | | RETURNS 1
| | recurse = 1
| | result = 1 * 1 [since n=1]
| | RETURNS 1 [returning result]
| recurse = 1 [catching returned result of 1]
| result = 2 * 1 [since n=2]
| RETURNS 2 [returning result]
recurse = 2 [catching returned result of 2]
result = 3 * 2 [since n=2]
RETURNS 6 [returning result]
答案 1 :(得分:1)
想想 reentrant 这个词的含义 - 这意味着可以多次输入该功能。调用该函数会阻止流中的特定位置,但它不会阻止该段代码在下一次调用中再次执行 。当链中的最后一个调用返回时,会解锁之前的调用,并且在链式反应中一切都被解除阻塞。
答案 2 :(得分:1)
在你理解其他一切都停止之前,你有点不正确。如果说,函数A调用函数B,则不是这样。在那种情况下,A将运行直到它调用B,此时它将暂停,B将运行,并且假设它不调用其他函数,则返回和A将恢复。
在递归的情况下,这意味着在每个级别(A,B,C,D ......)更深入,直到if子句允许函数N完成而不调用任何其他函数。从那时起,父函数将逐一恢复,直到你回到“main”,就像你所说的那样。
我正在打电话,因此输入一个例子有点麻烦。我回家后一定要写一个。也许如果您编写了一个打印“This is function X”的函数(并且可能添加一些缩进),您可以更好地将其可视化。
答案 3 :(得分:0)
比你想象的要少得多。对factorial
的每次调用都是相互独立的,每个调用都有自己的一组参数和局部变量。
让我们看一下递归的一个级别:
factorial(2)
Control将进入函数和else
块。在这里,对factorial
的内部调用发生了。因此控制将再次进入该函数,进入if
块并返回1.这结束了内部调用。
与此同时,外线电话已暂停。内部调用返回后,随着控件的继续,结果将存储在recurse
变量中。最终,外部调用返回2。
答案 4 :(得分:0)
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result
所以我猜您已经知道n
是本地的,因此factorial(2)
内factorial(3)
的调用不会影响第一次调用n
,以便{ {1}}将result = n * recurse
而不是result = 3 * 2
。
Python的范围规则是函数内定义的每个变量都是本地的。这意味着result = 1 * 1 * 1 * 1
和result
存在于recurse
的每个阶乘调用的几个版本中。
人们遇到问题的另一个问题是每次对阶乘的调用都是与最后一次完全不同的化身。实际上,调用一个完全不同的函数并没有什么不同:
n > 0
所以会发生的是,这个完全不同的函数的结果会导致比你需要的函数少一个......并且通过将def factorial(n):
if n == 0:
return 1
else:
return n * factorial2(n-1)
乘以{{的factorial2来恢复运行原始函数的延续。 1}}并返回该结果。
factorial2的代码非常相似,但它调用factorial3并且它再次调用factorial4 ..这样就没有递归只有n个不同的阶乘函数,每个阶乘函数都将它自己的参数与它的结果完全不同的函数相乘等待计算它自己的价值。这当然是愚蠢的,因为那时你将拥有许多相同的函数,只有不同的名称,并且调用自身(递归)与调用完全不同的函数没有任何不同。实际上你可以想到对同一个函数的每次调用,就好像它们是与前一个调用无关的完全不同的实例,因为它们不共享任何其他数据,而不是从调用者传递给被调用者作为参数,是什么作为结果从被调用者返回到调用者,就好像您要使用任何其他算术函数一样。只要答案是根据参数计算的,这将永远是真的。 (具有相同值的每个呼叫总是产生相同的答案,即功能)
答案 5 :(得分:-1)
正如Duffymo所说,两个部分都是等价的,第二部分只分为3行而不是1行。如果你能理解第一个,那么对第二个有什么不理解的呢?
recurse = factorial(n-1)
result = n * recurse
return result
与
相同 result = n * factorial(n-1)
return result
与
相同 return (n * factorial(n-1))
该函数将从发送的数字开始(比方说3),再次通过调用(n-1) - >(3,2,1,0)的函数向下移动,在0处返回1。然后我们在(3,2,1),我们使用1 * 1 = 1并返回。我们现在有(3,2)并使用2 * 1 = 2并返回。现在我们有(3),我们使用3 * 2 = 6,然后返回。随着()[什么都没有]我们完成了递归。