递归函数中会发生什么?

时间:2018-11-26 17:56:46

标签: python recursion

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

例如:

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如何显式管理递归,有什么地方可以参考的吗?

4 个答案:

答案 0 :(得分:5)

  

但是Python会自动求和吗?在所有情况下都可以吗?

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

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

n * fact(n-1)

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

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

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

此处的缩进表明函数是堆栈中每个单独的命名空间。当一个函数调用另一个函数时,当前函数被“暂停”,被保留,其包含的数据被放在栈顶,直到可以恢复为止。多个函数的调用使所有这些函数堆积起来,直到某些东西最终开始将结果返回给其调用者为止,此时,先前的函数可以从中断的地方继续执行,等等。

答案 1 :(得分:3)

没有魔术。让我们一步一步。

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。现在我们有了返回值,我们退回到上一次调用{{1} }:

fact(2)

我们说过# This is equivalent to return 2 * fact(1) return n * fact(n-1) 是1,所以我们返回2 * 1 = 2。

如果您学会使用debugger,则可以逐步进行操作,并清楚地看到自己会发生什么。如果您使用的是诸如PyCharm之类的IDE,则可能会内置一个调试器,使所有内容都易于可视化。

答案 2 :(得分:0)

希望这可以更好地说明这一点:

您有以下输出:

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

答案 3 :(得分:0)

递归函数是基本的编程概念,几乎所有的编程和脚本语言都可用。递归函数是一个循环,该循环创建一系列具有收益的函数。就像一个堆栈数据结构,后进先出

因此,在示例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。

希望这会有所帮助!