Language agnostic 在递归过程中会发生什么?
谁能告诉我递归程序中到底发生了什么 函数调用自身。这可以用来代替迭代,但通常效率较低,因为必须为每个函数调用分配新堆栈 与迭代相反,递归是一种算法或函数设计,其中函数调用自身。这会使函数更容易理解,但会使其速度变慢,因为必须创建新堆栈。此外,堆栈内存使用将随着每次调用而线性增加 另一方面,迭代在一个函数中循环,使时间复杂度保持在O(n),空间复杂度固定且不随每次迭代而增加Language agnostic 在递归过程中会发生什么?,language-agnostic,recursion,Language Agnostic,Recursion,谁能告诉我递归程序中到底发生了什么 函数调用自身。这可以用来代替迭代,但通常效率较低,因为必须为每个函数调用分配新堆栈 与迭代相反,递归是一种算法或函数设计,其中函数调用自身。这会使函数更容易理解,但会使其速度变慢,因为必须创建新堆栈。此外,堆栈内存使用将随着每次调用而线性增加 另一方面,迭代在一个函数中循环,使时间复杂度保持在O(n),空间复杂度固定且不随每次迭代而增加 例如,考虑一个添加连续数字的函数。请注意,在一个简单的计算中有一个公式可以做到这一点(时间复杂度为O(1)),但让我们只做一
例如,考虑一个添加连续数字的函数。请注意,在一个简单的计算中有一个公式可以做到这一点(时间复杂度为O(1)),但让我们只做一个例子
递归函数可能如下所示:long consecutive(long a) {
return a > 1 ? a + consecutive(a - 1) : a + 1;
}
def f(i):
if i > 100:
return 100
else:
return f(i+1)
print f(7)
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);
}
使用此递归调用可以很快耗尽堆栈内存。但是,迭代模型在这方面更好:
long consecutive(long a) {
long result = 0, i;
for (i = 1; i <= a; i++) result += i;
return result;
}
长连续(长a){
长结果=0,i;
对于(i=1;i函数反复调用自身,直到满足某些条件
一个愚蠢的例子:如何递归走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!"
请注意,同样的事情也可以通过循环(“迭代”)完成:
程序流程可能有点不同,但结果是相同的:
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!"
不能说递归还是迭代总是更好。在本例中,迭代(循环)比递归更易于编写和理解;在其他情况下(例如遍历树结构),递归可能是更好的解决方案。递归示例:
function foo(doRecurse)
{
alert("Foo was called");
if (doRecurse)
{
foo(false);
}
}
foo(true);
在本例中,当使用true调用foo时,它将再次使用false调用自身。因此,您将使用上述代码得到两条警报消息。函数foo调用函数foo的位是递归。递归程序可能如下所示:
long consecutive(long a) {
return a > 1 ? a + consecutive(a - 1) : a + 1;
}
def f(i):
if i > 100:
return 100
else:
return f(i+1)
print f(7)
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);
}
(python)
问题是,会发生什么?答案是最好从函数的角度来考虑,并试着把它们写出来
因此,我们将语句printf(7)
。为了找出它的计算结果,我们通过函数运行7
,发现它的计算结果是f(8)
。好的,很好,但是它的计算结果是什么?f(9)
。我故意在这里放了一个exit子句-最终I
将在循环中等于100,这将是返回的值。发生了什么:
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!"
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)是100
这是一个相当简单的示例,但它很好地演示了递归。正如其他人提到的,递归只是一个调用自身的方法。这有几个优点,但也有一些缺点
例如,阶乘的典型计算:
迭代版本:
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);
}
正如您所看到的,代码稍微短一些,并且稍微易于阅读(如果您习惯于递归)
现在让我们检查堆栈:
- 交互版本:3个整数变量(因子、当前因子、结果)
- 递归版本:
1:1变量的阶乘(因子)
2:2变量的阶乘(阶乘的因子和返回值(因子-1))
3的阶乘:3个变量(因子,具有另一个阶乘(因子-1)返回值的阶乘(因子-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
正如您所见,递归在堆栈上需要更多的空间(您必须添加返回指针、变量的push和pop以及函数调用所需的其他内容)通常速度较慢。通过使用尾部优化递归可以减少空间问题,但这对于本文来说是一个太大的主题,但为了完整性,优化版本如下所示:
long consecutive(long a) {
return a > 1 ? a + consecutive(a - 1) : a + 1;
}
def f(i):
if i > 100:
return 100
else:
return f(i+1)
print f(7)
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);
}
递归唯一真正有用的地方是(IMHO)遍历树结构。@Pekka:hahahahahahahaha!但要小心!如果没有breakout子句,可能会导致堆栈溢出stackoverflow@Matt雅各布森:哟,老兄,我喜欢你…;)或者更高效,因为不需要计数器来管理迭代。总体而言,效率可能会稍低。创建新堆栈的时间和空间复杂度要比增加计数器复杂得多。此外,堆栈内存空间非常有限,因此在出现分段错误之前可用的递归数通常都是有限的很小。谢谢你给我举了这么一个很棒的例子。这是我这一周看到的最好的例子。