递归中的返回语句

时间:2012-09-21 05:46:58

标签: python recursion

所以我对递归的概念有一个相当不错的理解,但是有些实现真的让我感到震惊。以这个简单的斐波纳契函数为例:

def fib(x):
    if x == 0 or x == 1:
        return 1
    else:
        return fib(x-1) + fib(x-2)

我知道这会将斐波纳契计算分解成更小的更易处理的块。但它究竟是如何达到最终结果的呢?在递归情况下返回的确切内容是什么?看起来它只是返回一个函数调用,它将继续调用函数直到它返回1 - 但它似乎永远不会做任何真正的计算/操作。将其与经典的因子函数进行对比:

def factorial(n):
    if n == 1:
         return 1
    else:
         return n * factorial(n) 

这里,函数显然是每次运行n,一个定义的整数,而fibonacci函数只对函数本身进行操作,直到返回1。

最后,当我们带来类似Merge Sort算法的东西时,事情变得更加怪异;即这一段代码:

    middle = int(len(L)/2)
    left = sort(L[:middle], lt)
    right = sort(L[middle:], lt)
    print(left, right)
    return merge(left, right, lt)

左右似乎是递归调用sort,但print语句似乎表明merge正在处理每个递归调用。所以每个递归调用都是以某种方式"已保存"然后在返回时最终调用merge时进行操作?我越来越混淆自己的第二个......我觉得我已经处于对递归的强烈理解的边缘,但我对递归调用的确切回复的理解是站在我的办法。

3 个答案:

答案 0 :(得分:9)

尝试这个练习:

fib(0)的价值是多少? fib(1)的价值是多少?让我们把它们写下来。

fib(0) == 1
fib(1) == 1

我们知道这是因为这些是“基本案例”:它与 fib 定义中的第一个案例相匹配。

好的,让我们提一下吧。 fib(2)的价值是多少?我们可以看一下函数的定义,它将是:

fib(2) == fib(1) + fib(0)

我们知道 fib(1) fib(0)的价值是什么:这些都会做一些工作,然后给我们一个答案。所以我们知道 fib(2)最终会给我们一个价值。

好的,搞砸了。 fib(3)的价值是多少?我们可以看看定义,它将是:

fib(3) == fib(2) + fib(1)

我们已经知道 fib(2) fib(1)最终将为我们计算数字。 fib(2)会比 fib(1)做更多的工作,但它们最终都会触底来给我们添加的数字。

首先查看小案例,看看当你提出问题的大小时,子问题就是我们知道如何处理的问题。

如果你已经完成了标准的高中数学课程,你将会看到类似的东西:数学家使用所谓的“数学归纳”,这与我们程序员用作工具的递归相同。

答案 1 :(得分:6)

不了解递归函数的工作方式很常见,但它确实表明你只是不理解函数和返回的工作方式,因为递归函数与普通函数的工作方式相同完全

print 4

这是有效的,因为print语句知道如何打印值。它被赋予值4,并打印出来。

print 3 + 1

print语句无法理解如何打印3 + 13 + 1不是一个值,它是一个表达式。幸运的是,print不需要知道如何打印表达式,因为它永远不会看到它。 Python将传递给事物,而不是表达式。所以Python在执行代码时评估表达式是做什么的。在这种情况下,这会导致生成值4。然后将值4赋予print语句,并快乐地打印它。

def f(x):
    return x + 1

print f(3)

这与上面非常相似。 f(3)是表达式,而不是值。 print对此无能为力。 Python必须评估表达式以产生一个值来打印。它通过查找名称f来实现,幸运的是找到def语句创建的函数对象,并使用参数3调用该函数。

这导致函数的正文被执行,x绑定到3。与print的情况一样,return语句不能对表达式x + 1执行任何操作,因此Python会对该表达式求值以尝试查找值。 x + 1x绑定3的{​​{1}}会生成值4,然后返回该值。

从函数返回值使得函数调用表达式的求值变为该值。因此,在print f(3)中退出,Python已成功将表达式f(3)计算为值4。然后可以打印print

def f(x):
    return x + 2

def g(y):
    return f(y * 2)

print g(1)

此处,g(2)表达式不是值,因此需要对其进行评估。评估g(2)会导致我们f(y * 2) y绑定1y * 2不是值,因此我们无法在其上调用f;我们必须首先评估它,它产生值2。然后,我们可以在f上致电2x + 2返回x2绑定到x + 24评估值f,该值从f(y * 2)返回,并在g内成为g表达式的值。这个 finally 给出了g(1)返回的值,因此表达式4将被计算为值f(2),然后打印出来。

请注意,当向下钻取以评估g(1)时,Python仍然“记住”它已经在评估f(2)的过程中,并且一旦知道了{{1}它就会回到正确的位置评估为。

就是这样。这就是全部。您不需要了解有关递归函数的任何特殊内容。 return使调用函数特定调用的表达式成为赋给return的值。 立即表达式,而不是一个调用调用函数的函数的函数的高级表达式。最里面的一个。 无关紧要中间函数调用是否恰好与此函数相同。 return无法知道是否以递归方式调用此函数,更不用说在这两种情况下表现不同了。 return 始终始终将其值返回给此函数的直接调用方,无论它是什么。 永远不会永远“跳过”任何这些步骤,并将值更多地返回给调用者(例如递归函数的最外层调用者)。

但为了帮助您这是有效的,让我们更详细地浏览fib(3)的评估。

fib(3):
    3 is not equal to 0 or equal to 1
    need to evaluate fib(3 - 1) + fib(3 - 2)
        3 - 1 is 2
        fib(2):
            2 is not equal to 0 or equal to 1
            need to evaluate fib(2 - 1) + fib(2 - 2)
                2 - 1 is 1
                fib(1):
                    1 is equal to 0 or equal to 1
                    return 1
                fib(1) is 1
                2 - 2  is 0
                fib(0):
                    0 is equal to 0 or equal to 1
                    return 1
                fib(0) is 1
            so fib(2 - 1) + fib(2 - 2) is 1 + 1
        fib(2) is 2
        3 - 2 is 1
        fib(1):
            1 is equal to 0 or equal to 1
            return 1
        fib(1) is 1
    so fib(3 - 1) + fib(3 - 2) is 2 + 1
fib(3) is 3

更简洁,fib(3)返回fib(2) + fib(1)fib(1)返回1,但fib(3)会返回加上 fib(2)的结果。 fib(2)返回fib(1) + fib(0);这两个都返回1,因此将它们添加到一起会fib(2) 2的结果。回到fib(3) fib(2) + fib(1),我们现在可以说2 + 13

您遗漏的关键点是,当fib(0)fib(1)返回1时,那些1构成了较高级别调用累加的表达式的一部分。

答案 2 :(得分:2)

您需要了解数学归纳才能真正掌握这一概念。一旦理解,递归就简单明了。考虑一个简单的函数,

     def fun(a):  
          if a == 0: return a
          else return a + 10

返回声明在这里做什么?它只返回a+10。为什么这很容易理解?当然,一个原因是它没有递归。;)为什么返回语句如此容易理解,它在调用时有一个和10可用。

现在,考虑一个使用递归的简单sum of n numbers程序。现在,在编写递归之前,一个重要的事情是你必须理解它应该在数学上如何工作。对于n个数字的总和,我们知道如果已知sumn-1个数字,我们可以返回sum + n。现在如果不知道sum怎么办?好吧,我们会找到n-2个术语的总和并添加n-1

所以,sumofN(n) = n + sum(n-1)

现在,终止部分。我们知道这无法无限期地继续下去。因为sumofN(0) = 0

所以,

 sumofN(n) = 0, if n = 0,  
            n + sum(n-1) , otherwise

在代码中这意味着,

      def sumofN(n):  
          if n == 0: return 0  
          return n + sum(n-1)

这里假设我们称为sumofN(10)。它返回10 + sumofN(9)。我们10与我们在一起。另一个术语怎么样?它是其他一些函数的返回值。所以我们要做的就是等到函数返回。这里,由于被调用的函数本身就是它,它等待sumofN(9)返回。当我们达到9 + sumofN(8)时,它等到sumofN(8)返回。

实际发生的是

  

10 + sumofN(9),即为   10 + 9 + sumofN(8),即为   10 + 9 + 8 + sumofN(7).....

最后当sumofN(0)返回时,

  

10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0 = 55

理解递归需要这个概念。 :)。 现在,mergesort呢?

   mergesort(someArray)  = { l = mergesort the left part of array,
                             r = mergesort the right part of the array,  
                             merge(l and r)  
                           }

直到左侧部分可以返回,它继续在“leftest”数组上调用mergesort。一旦我们有了这个,我们找到了确实找到“leftest”数组的正确数组。一旦我们左右两侧合并它们。

关于递归的一件事是,一旦你从正确的角度看它并且正确的视角被称为数学归纳

,它就太容易了。