用小例子理解递归

Understanding recursion with small example

本文关键字:递归      更新时间:2023-10-16

Hi我在cpp中写了这样的小递归方法。我正在尝试理解递归

void print(int n)
{
    if(n==6)
        return;
    print(++n);
    cout<<n<<endl;
    //output is 6 5 4 3 2
}
void print(int n)
{
    if(n==6)
        return;
    print(n+1);
    cout<<n<<endl;
    //output is  5 4 3 2 1
}
void print(int n)
{
    if(n==6)
        return;
    print(n++);
    cout<<n<<endl;
    //programme crash
}

你能解释一下内部发生了什么吗?

函数调用放置在堆栈上。把它想象成一堆盘子。任何时候在代码中调用"print(x)",它都是对这堆板的又一次添加。当函数到达其右大括号或到达返回语句时,函数将从堆栈中删除。

我认为您正在对这些函数调用print(0)。因此,print(0)是堆栈上的第一个内容。最后一个函数崩溃是因为它"永远"调用print(0),直到它没有空间容纳更多的"板"。这被称为无限递归,由于堆栈的限制,无限递归很少是无限的。

至于其他函数,"cout"语句只有在从堆栈中删除函数之后才被调用。除了print(6)调用这一个例外,这些方法中的每一个都不断在堆栈中放置新的东西。这通常被称为基本情况,是递归过程的结束。因为它开始从堆栈中级联移除板,从而允许所有cout语句发生(与无限递归的情况不同)。

要了解这些代码的区别,您必须确保了解n++、++n和n+1之间的区别。

这不是递归中的问题,而是表达式求值中的问题。我们有三个例程,当n达到6时停止;否则,它们会进行某种形式的递增和重复,并使用不同的表达式。递归调用后,打印n的本地值并返回给调用者。

请注意,每次调用print时,都会向运行时堆栈添加一个新的局部变量空间。每一个都有自己的n副本:增加一个副本不会改变其他副本。

  • ++n增加n
  • n+1不要更改n的本地副本,而是使用下一个更高的值再次调用该函数
  • ++n使用当前n的本地值,然后打印

有时,尝试并说明调用序列会有所帮助。

想象一个函数f1到f7的序列,其中f1调用f2,后者调用f3,依此类推。(因为我从0开始,而不是1)

f1看起来像:(和其他非常相似)

void f1(int n)
{ 
   if(n==6)
      return;
   f2(++n); 
   std::cout<<n<<std::endl;
}

因此,由f1(0)发起的呼叫序列可以被说明为:

f1(0)--v                : because f2 is called with ++n
       f2(1)--v         : because f3 is called with ++n etc.
              f3(2)--v
                     f4(3)--v
                            f5(4)--v
                                   f6(5)--v
                                          f7(6)

每个函数调用都有一个返回:

                                          // no cout of 7
                                   return to f6(6)  
                                   cout<<... n is 6
                            return to f5(5)
                            cout<<... n is 5
                     return to f4(4)
                     cout<<... n is 4
              return to f3(3)
              cout<<... n is 3
       return to f2(2)
       cout<<... n is 2
return to f1(1)
cout<<... n is 1

现在,要使用递归,只需将编号的f替换为foo

void foo(int n)
{ 
   if(n==6)
      return;
   foo(++n); 
   std::cout<<n<<std::endl;
}

并且调用序列(用foo(0)启动)可能看起来像

foo(0)--v               -- given 0, calls foo with 1
        foo(1)--v              -- give 1, calls foo with 2
                foo(2)--v             -- etc
                        foo(3)--v
                                foo(4)--v
                                        foo(5)--v
                                                foo(6) - return

我称第一部分递归为

                                        return to foo(6)  (was 5)
                                        cout<<... n is 6
                                return to foo(5)  (was 4)
                                cout<<... n is 5
                        return to foo(4)  (was 3)
                        cout<<... n is 4
                return to foo(3)  (was 2)
                cout<<... n is 3
        return to foo(2)  (was 1)
        cout<<... n is 2
return to foo(1)  (was 0)
cout<<... n is 1

第二部分,所有这些函数的返回,这个特定堆栈使用的"崩溃",我有时会注意到"decursion"(旧军事术语的新用法)。(++运算符的影响使对齐具有误导性。)

因为cout在递归调用之后,(即在递归中)即使输入'n'在递归过程中增加,输出序列也在减少。

因此,这个特定的序列每行包含1位数字:

6
5
4
3
2
1

更新-为什么上次代码剪切崩溃?

最后一个代码片段崩溃,因为后增量发生在递归调用之后。因此,调用foo(0)确实调用了foo(n++),但这只是再次调用foo,然后增加n(再次)。第二个和所有后续的foo()递归都看到相同的值0,因此终止条件(n=6)永远不会发生,堆栈溢出了无限函数调用。