任何人都可以告诉我在递归程序中到底发生了什么...... ??
答案 0 :(得分:11)
一个函数一遍又一遍地调用自身,直到满足某些条件。
一个愚蠢的例子:如何递归地走100步:
function walk(steps) {
if (steps > 0) { // not there yet
take 1 step
walk(steps - 1); // we're 1 step closer, now walk the rest of the way
} else { // we're there already, don't walk any further than requested
echo "You're there!"
}
}
walk(100); // go!
这就是:
walk(100):
take 1 step
walk(99):
take 1 step
walk(98):
take 1 step
walk(97):
(...)
walk(2):
take 1 step
walk(1):
take 1 step
walk(0):
// "steps > 0" no longer true, use the "else" branch
echo "You're there!"
请注意,使用循环(“迭代”)可以完成同样的事情:
function walk(steps) {
for (c=steps; c > 0; c--) { // note the condition check - "c > 0"
take 1 step
}
echo "You're there!"
}
walk(100); // go!
程序流程会有所不同,但结果是一样的:
walk(100):
c=100
take 1 step
c=99
take 1 step
c=98
take 1 step
(...)
c=2
take 1 step
c=1
take 1 step
c=0
// "c > 0" no longer true, exit loop
echo "You're there!"
不能说递归或迭代总是更好。在这个例子中,迭代(循环)比递归更容易编写和更容易理解;在其他情况下(例如遍历树结构),递归可能是更好的解决方案。
答案 1 :(得分:3)
与迭代相反,递归是一种函数调用自身的算法或函数设计。这可以使函数更容易理解,但是因为必须创建新的堆栈会使它变慢。此外,堆栈内存使用量将随着每次调用而线性增加。
另一方面,迭代在一个函数中循环,将时间复杂度保持在O(n),并且空间复杂度固定而不是随着每次迭代而增加。例如,考虑添加连续数字的函数。请注意,有一个公式可以通过一个简单的计算(O(1)时间复杂度)来执行此操作,但我们只是以此为例。
递归函数可能如下所示:
long consecutive(long a) {
return a > 1 ? a + consecutive(a - 1) : a + 1;
}
使用此递归调用可能会很快耗尽堆栈内存。但是,迭代模型对此更好:
long consecutive(long a) {
long result = 0, i;
for (i = 1; i <= a; i++) result += i;
return result;
}
答案 2 :(得分:1)
递归的一个例子:
function foo(doRecurse)
{
alert("Foo was called");
if (doRecurse)
{
foo(false);
}
}
foo(true);
在这个例子中,当foo被调用为true时,它会再次使用false调用自身。因此,您将获得上述代码的两条警报消息。函数foo调用函数foo的位是递归。
答案 3 :(得分:1)
递归程序可能如下所示:
def f(i):
if i > 100:
return 100
else:
return f(i+1)
print f(7)
(蟒)
问题是,会发生什么?答案是最好在功能方面考虑这个问题并尝试将其写出来。
所以,我们有声明print f(7)
。为了找出这个评估的内容,我们通过函数运行7
并找出它的计算结果为f(8)
。好的,但是评估的是什么? f(9)
。我故意在这里有一个退出条款 - 最终i
将在循环中等于100,这将是返回的内容。会发生什么:
f(7) = ?
f(7) = f(8) = ?
f(7) = f(8) = f(9) = ?
f(7) = f(8) = f(9) = f(10) = ?
...
f(7) = f(8) = ... = 100
f(7) is 100
这是一个相当简单的例子,但确实证明了递归。
答案 4 :(得分:1)
正如其他人所提到的,递归只是一种自称的方法。这有几个优点,但也有一些缺点。
例如典型的阶乘计算:
迭代版本:
int factorial(int factor)
{
int result = 1;
int current_factor = 1;
while (current_factor < factor)
{
result *= current_factor;
++current_factor;
}
return result;
}
递归版:
int factorial(int factor)
{
if (factor == 1) return 1;
return factor * factorial(factor - 1);
}
正如您所看到的,代码更短,更容易阅读(如果您习惯于递归)。
现在让我们检查一下堆栈:
看起来像这样粗鲁
factorial(5):
factorial(4):
factorial(3):
factorial(2):
factorial(1):
1
2+1
3+2+1
4+3+2+1
5+4+3+2+1
正如您所看到的,递归需要更多的空间在堆栈上(您必须添加返回指针,推送和弹出变量以及函数调用所需的其他东西)并且它通常较慢。使用尾部优化的递归可以减少空间问题,但这对于这篇文章来说太大了,但为了完整性,优化版本看起来像这样:
int factorial(int current, int end, int *result) // result is a pointer, so changes
// on result affect the original variable
{
if (current > end) return;
*result = *result * current;
factorial(current + 1, end, result);
}
递归真正有用的唯一地方是(恕我直言)穿过树木结构。
答案 5 :(得分:0)
一个函数调用自身。这可以用来代替迭代,但通常效率较低,因为必须为每个函数调用分配新的堆栈。