【问题标题】:What explicitly happens in a recursive function?递归函数中明确发生了什么?
【发布时间】:2018-11-26 17:56:45
【问题描述】:

我在一定程度上理解递归的概念,但我无法理解递归调用中发生的所有步骤。

例如:

def fact(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact({}) = {} * fact({})'.format(n,n,n,n-1))
        return n * fact(n-1)

answer = int (input('Enter some number: '))
print(fact(answer))

>> Enter some number: 5
5 is not 0, so fact(5) = 5 * fact(4)
4 is not 0, so fact(4) = 4 * fact(3)
3 is not 0, so fact(3) = 3 * fact(2)
2 is not 0, so fact(2) = 2 * fact(1)
1 is not 0, so fact(1) = 1 * fact(0)
120

虽然我知道它会重复任务直到它到达 n == 0 的基础,但是 Python 如何存储以前的 5 * 4 * 3 ... 并在到达基础时进行计算,我发现将整个过程可视化有点困难.

另一个例子是当我传递一个可迭代对象时。

def getSum(piece):
    if len(piece) == 0:
        return 0
    else:
        print(piece)
        return piece[0] + getSum(piece[1:]) 
print(getSum([1, 3, 4, 2, 5]))
>> 
[1, 3, 4, 2, 5]
[3, 4, 2, 5]
[4, 2, 5]
[2, 5]
[5]
15

列表似乎从每次递归减少到piece[n-1:],最终所有返回的值都被求和了。有什么地方可以参考 Python 如何显式管理递归?

【问题讨论】:

  • 任何其他函数调用中也没有发生任何事情。
  • Python 递归与任何其他语言的递归没有什么不同。
  • Python 不求和。你告诉 Python 将n 与函数的返回值相乘n * fact(n - 1)。它执行了函数调用,然后执行了n * <result> 部分。
  • 如果您发现它令人困惑,请尝试在纸上手动“运行”算法,只需少量输入。例如,对于fact(3),当您到达递归调用3 * fact(3-1) 时,将fact(3-1) 替换为x,并以相同的方式开始计算x (fact(2))。您会发现x2,因此返回值是3 * 2,即6。 Python 没有做任何没有明确写在函数中的特殊操作。
  • 马丁·彼得斯。抱歉,我的意思不是sum,但我真的想将整个过程可视化,就像return n * fact(n-1) 没有在每个递归级别分配给某个标识符,它弄乱了我试图查看它的方式。

标签: python recursion


【解决方案1】:

但是 Python 会自动对金额求和吗?

Python 在这里不需要做任何特别的事情。递归函数调用只是函数调用。函数调用没有什么神奇之处。

所发生的只是a函数调用的返回值被用于乘法:

n * fact(n-1)

Python 执行了fact(n-1) 函数调用,函数返回,Python 通过将n 与返回值相乘来完成表达式。

将此与您可以调用的任何其他函数进行比较。 n * math.sin(n-1) 会更容易理解吗?您不必知道math.sin()inside 是什么,只需知道它返回一个值,然后将其用于乘法。

fact() 函数调用与这里的函数完全相同实际上并不重要。 Python 不在乎。 Python 完全不在乎,因为 Python 是如此动态。从某一刻到下一刻,名称fact 可能绑定到不同的东西,所以Python 只是在名称表中查找fact,并使用n-1 的结果调用它。这里没有特别考虑fact 指向与当前执行的函数相同的函数。

为每个步骤创建单独的函数可能有助于理解

def fact5(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact5({}) = {} * fact4({})'.format(n,n,n,n-1))
        return n * fact4(n-1)

def fact4(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact4({}) = {} * fact3({})'.format(n,n,n,n-1))
        return n * fact3(n-1)

def fact3(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact3({}) = {} * fact2({})'.format(n,n,n,n-1))
        return n * fact2(n-1)

def fact2(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact2({}) = {} * fact1({})'.format(n,n,n,n-1))
        return n * fact1(n-1)

def fact1(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact1({}) = {} * fact0({})'.format(n,n,n,n-1))
        return n * fact0(n-1)

def fact0(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact0({}) = {} * fact_minus1({})'.format(n,n,n,n-1))
        return n * fact_minus1(n-1)

然后调用fact5(5)获取

>>> fact5(5)
5 is not 0, so fact5(5) = 5 * fact4(4)
4 is not 0, so fact4(4) = 4 * fact3(3)
3 is not 0, so fact3(3) = 3 * fact2(2)
2 is not 0, so fact2(2) = 2 * fact1(1)
1 is not 0, so fact1(1) = 1 * fact0(0)
120

请注意,我没有费心定义 fact_minus1() 函数,我们知道当您以 fact5(5) 开头时,它不会被调用。

您还可以在可视化中添加更多信息。您不记录函数的返回值,您可以添加缩进以可视化您在调用结构中的深度:

def fact(n, _indent=''):
    print(f"{_indent}-> fact({n}) called")
    if n == 0:
        print(f"{_indent}<- fact({n}) returns 1")
        return 1
    else:
        print(f"{_indent}   fact({n}) calls fact({n-1}) ->")
        recursive_result = fact(n - 1, _indent+'  ')
        print(f"{_indent}   fact({n}) <- received {recursive_result}, multiplying with {n}")
        result = n * recursive_result
        print(f"{_indent}<- fact({n}) returns {result}")
        return result

产生:

-> fact(5) called
   fact(5) calls fact(4) ->
  -> fact(4) called
     fact(4) calls fact(3) ->
    -> fact(3) called
       fact(3) calls fact(2) ->
      -> fact(2) called
         fact(2) calls fact(1) ->
        -> fact(1) called
           fact(1) calls fact(0) ->
          -> fact(0) called
          <- fact(0) returns 1
           fact(1) <- received 1, multiplying with 1
        <- fact(1) returns 1
         fact(2) <- received 1, multiplying with 2
      <- fact(2) returns 2
       fact(3) <- received 2, multiplying with 3
    <- fact(3) returns 6
     fact(4) <- received 6, multiplying with 4
  <- fact(4) returns 24
   fact(5) <- received 24, multiplying with 5
<- fact(5) returns 120
120

这里的缩进表明每个函数都是堆栈上的独立命名空间。当一个函数调用另一个函数时,当前函数被“暂停”,被搁置,它包含的数据被放在堆栈顶部,直到它可以恢复。多个函数调用,所以所有函数都堆积起来,直到某些东西最终开始将结果返回给它们的调用者,此时前一个函数可以从它们停止的地方继续,等等。

【讨论】:

  • 抱歉造成混乱,虽然它仍然存在,但我很难理解n * fact(n-1) 的存储位置,同时还要进入下一个级别。
  • @BernardL:与其他函数的存储方式相同。 Python 将函数数据保存在 stack 上。函数A调用函数B?然后 Python 将关于函数 A 的所有内容都放在一个堆栈中,并开始执行函数 B。当函数 B 返回时,从堆栈顶部继续 Python 离开的地方。
  • 超级棒的解释Martijn。我真的对返回结果的位置和位置感到困惑。据我了解,函数fact 被多次调用,即paused,并在收到第一个返回值后恢复。
【解决方案2】:

没有魔法。让我们一步一步来。

def fact(n):
    if n == 0:
        return 1
    else:
        print('{} is not 0, so fact({}) = {} * fact({})'.format(n,n,n,n-1))
        return n * fact(n-1)

我假设您了解 fact(0) 会发生什么,所以我不会详细介绍。我们来看看fact(2)

def fact(n):      # n = 2
    if n == 0:    # Skipping this case
        return 1
    else:
        # This is equivalent to return 2 * fact(1)
        return n * fact(n-1)

现在我们进入fact(1)

def fact(n):      # n = 1
    if n == 0:    # Skipping this case
        return 1
    else:
        # This is equivalent to return 1 * fact(0)
        return n * fact(n-1)

当然,fact(0) 返回 1,所以 fact(1) 返回 (1 * 1) = 1。现在我们有了返回值,我们退回到最后一次调用 fact(2)

# This is equivalent to return 2 * fact(1)
return n * fact(n-1)

正如我们所说,fact(n-1) 是 1,所以我们返回 2 * 1 = 2。

如果您学会使用 debugger,您将能够逐步完成并清楚地看到自己发生了什么。如果您使用的是 PyCharm 之类的 IDE,它可能会内置一个调试器,使一切都易于可视化。

【讨论】:

  • 学习使用调试器怎么强调都不为过——它不仅有帮助,它实际上是任何程序员的必备工具。我在这个网站上看到了很多很多问题,如果 OP 在调试器中使用他们的程序,这些问题可以很容易地解决。
【解决方案3】:

希望这能更好地说明这一点:

你有这个输出:

5 is not 0, so fact(5) = 5 * fact(4)
4 is not 0, so fact(4) = 4 * fact(3)
3 is not 0, so fact(3) = 3 * fact(2)
2 is not 0, so fact(2) = 2 * fact(1)
1 is not 0, so fact(1) = 1 * fact(0)

我们从fact(5) = 5 * fact(4)开始

fact(4) 实际上是 4 * fact(3)(依此类推,直到 n==0)

如果我们实际上写出了整个递归行(5),那将是:

5 * fact(4) * fact(3) * fact(2) * fact(1) * fact(0) #which is 1, base case

其实是……

5 * (4*fact(3)) * (3*fact(2)) * (2*fact(1)) * (1*fact(0)) # 1*fact(0) == 1*1

简化的是...

5 * 4 * 3 * 2 * 1 = 120

【讨论】:

    【解决方案4】:

    递归函数是基本的编程概念,几乎适用于所有编程和脚本语言。递归函数是一个循环,它创建一系列返回时产生的函数。类似于 Stack 的数据结构后进先出

    所以,在示例 1 中,堆栈是

    1 * return fact(0)  // return to next statement in stack
    2 * return fact(1)  // return to next statement in stack
    3 * return fact(2)  // ....
    4 * return fact(3)
    5 * return fact(4)
    

    所以,最后 4 * fact(3) 将返回 24,这将是 fact(4) 的返回值,并且, 因此 5 * return fact(4) = 120。

    希望这会有所帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-12
      • 1970-01-01
      • 1970-01-01
      • 2014-10-02
      • 2020-08-22
      • 1970-01-01
      • 2020-11-03
      • 2018-01-05
      相关资源
      最近更新 更多