Language agnostic 在递归过程中会发生什么?

Language agnostic 在递归过程中会发生什么?,language-agnostic,recursion,Language Agnostic,Recursion,谁能告诉我递归程序中到底发生了什么 函数调用自身。这可以用来代替迭代,但通常效率较低,因为必须为每个函数调用分配新堆栈 与迭代相反,递归是一种算法或函数设计,其中函数调用自身。这会使函数更容易理解,但会使其速度变慢,因为必须创建新堆栈。此外,堆栈内存使用将随着每次调用而线性增加 另一方面,迭代在一个函数中循环,使时间复杂度保持在O(n),空间复杂度固定且不随每次迭代而增加 例如,考虑一个添加连续数字的函数。请注意,在一个简单的计算中有一个公式可以做到这一点(时间复杂度为O(1)),但让我们只做一

谁能告诉我递归程序中到底发生了什么

函数调用自身。这可以用来代替迭代,但通常效率较低,因为必须为每个函数调用分配新堆栈

与迭代相反,递归是一种算法或函数设计,其中函数调用自身。这会使函数更容易理解,但会使其速度变慢,因为必须创建新堆栈。此外,堆栈内存使用将随着每次调用而线性增加

另一方面,迭代在一个函数中循环,使时间复杂度保持在O(n),空间复杂度固定且不随每次迭代而增加

例如,考虑一个添加连续数字的函数。请注意,在一个简单的计算中有一个公式可以做到这一点(时间复杂度为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)

问题是,会发生什么?答案是最好从函数的角度来考虑,并试着把它们写出来

因此,我们将语句print
f(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雅各布森:哟,老兄,我喜欢你…;)或者更高效,因为不需要计数器来管理迭代。总体而言,效率可能会稍低。创建新堆栈的时间和空间复杂度要比增加计数器复杂得多。此外,堆栈内存空间非常有限,因此在出现分段错误之前可用的递归数通常都是有限的很小。谢谢你给我举了这么一个很棒的例子。这是我这一周看到的最好的例子。