所以我对递归的概念有一个相当不错的理解,但是有些实现真的让我感到震惊。以这个简单的斐波纳契函数为例:
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时进行操作?我越来越混淆自己的第二个......我觉得我已经处于对递归的强烈理解的边缘,但我对递归调用的确切回复的理解是站在我的办法。
答案 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 + 1
。 3 + 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 + 1
与x
绑定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
绑定1
。 y * 2
不是值,因此我们无法在其上调用f
;我们必须首先评估它,它产生值2
。然后,我们可以在f
上致电2
,x + 2
返回x
,2
绑定到x + 2
。 4
评估值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 + 1
即3
。
您遗漏的关键点是,当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
个数字的总和,我们知道如果已知sum
个n-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”数组的正确数组。一旦我们左右两侧合并它们。
关于递归的一件事是,一旦你从正确的角度看它并且正确的视角被称为数学归纳
,它就太容易了。