3

所以我对递归的概念有相当不错的理解,但有些实现真的让我很困惑。以这个简单的斐波那契函数为例:

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 进行操作,而斐波那契函数只对函数本身进行操作,直到返回 1。

最后,当我们使用合并排序算法之类的东西时,事情会变得更加奇怪。即这段代码:

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

left 和 right 似乎在递归调用 sort,但 print 语句似乎表明 merge 正在处理每个递归调用。那么每个递归调用是否以某种方式“保存”,然后在最终在返回时调用合并时对其进行操作?我越来越困惑自己......我觉得我正处于对递归的强烈理解的边缘,但我对 return 对递归调用的确切作用的理解阻碍了我的发展。

4

3 回答 3

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)做更多的工作,但它们最终都会触底,给我们可以添加的数字。

先解决小问题,看看当你扩大问题的规模时,子问题是我们知道如何处理的事情。

如果你上过标准的高中数学课,你就会看到类似的东西:数学家使用所谓的“数学归纳法”,这与我们程序员用作工具的递归的想法相同。

于 2012-09-21T06:08:59.420 回答
9

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

print 4

这是有效的,因为该print语句知道如何打印值。它被赋予了 value 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 必须对表达式求值以产生一个值来打印。它通过查找 name 来做到这一点,f幸运的是,它找到了由def语句创建的函数对象,并使用参数调用函数3

这导致函数的主体被执行,并x绑定到3. 与 的情况一样print,该return语句不能对表达式执行任何操作x + 1,因此 Python 会评估该表达式以尝试找到一个值。x + 1with xbound to3产生 value 4,然后返回。

从函数返回一个值会使函数调用表达式的求值变为该值。所以,回到 中print f(3),Python 已经成功地将表达式计算f(3)为 value 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它;我们必须首先评估它,这会产生 value 2。然后我们可以调用fon ,2它返回bound to 。计算为 value ,该 value 是从内部返回并成为内部表达式的值。这最终给出了一个返回值,因此表达式被评估为 value ,然后打印出来。x + 2x2x + 24ff(y * 2)ggg(1)4

请注意,在深入评估f(2)Python 时,仍然“记得”它已经在评估的中间,g(1)一旦知道f(2)评估的内容,它就会回到正确的位置。

就是这样。这就是全部。您不需要了解有关递归函数的任何特殊内容。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)返回1加上的结果fib(2)fib(2)返回fib(1) + fib(0);两者都返回1,因此将它们加在一起fib(2)得到2. 回到fib(3)过去fib(2) + fib(1),我们现在可以说这就是2 + 1过去3

您缺少的关键点是 whilefib(0)fib(1)return 1,它们1构成了更高级别调用相加的表达式的一部分。

于 2012-09-21T06:41:10.440 回答
3

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

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

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

现在,考虑一个使用递归的简单sum of n numbers程序。现在,在对递归进行编码之前,一件重要的事情是您必须了解它在数学上应该如何工作。在n数字总和的情况下,我们知道如果数字是已知的,我们可以返回sum它。现在如果不知道怎么办。好吧,我们找到项的总和并添加到它。n-1sum + nsumn-2n-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(someArray)  = { l = mergesort the left part of array,
                             r = mergesort the right part of the array,  
                             merge(l and r)  
                           }

在可以返回左侧部分之前,它会继续在“最左侧”数组上调用合并排序。一旦我们有了它,我们就找到了正确的数组,它确实找到了“最左边的”数组。一旦我们有一个左右,我们合并它们。

关于递归的一件事是,一旦你从正确的角度看待它,它就非常简单,而正确的角度被称为数学归纳法

于 2012-09-21T08:45:23.157 回答