【问题标题】:Understanding recursion in the beer bottle example了解啤酒瓶示例中的递归
【发布时间】:2013-12-21 01:58:08
【问题描述】:

我正在自己练习 C 中的递归,我在网上找到了这个示例。 但是有一点我不明白。

void singSongFor(int numberOfBottles)
{
if (numberOfBottles == 0) {
    printf("There are simply no more bottles of beer on the wall.\n\n");
} 
else {
    printf("%d bottles of beer on the wall. %d bottles of beer.\n",
           numberOfBottles, numberOfBottles);
    int oneFewer = numberOfBottles - 1;
    printf("Take one down, pass it around, %d bottles of beer on the wall.\n\n",
           oneFewer);
    singSongFor(oneFewer); // This function calls itself!

    // Print a message just before the function ends
    printf("Put a bottle in the recycling, %d empty bottles in the bin.\n",
             numberOfBottles);
   }
}    

然后我使用这样的主要方法:

 int main(int argc, const char * argv[])
{
  singSongFor(4);
  return 0;
}

输出是这样的:

墙上有 4 瓶啤酒。 4瓶啤酒。 拿下一个,传过来,墙上有 3 瓶啤酒。

墙上有 3 瓶啤酒。 3瓶啤酒。 拿下一个,传过来,墙上有 2 瓶啤酒。

墙上有 2 瓶啤酒。 2瓶啤酒。 拿下一个,传过来,墙上有 1 瓶啤酒。

墙上有 1 瓶啤酒。 1瓶啤酒。 拿下一个,传过来,墙上0瓶啤酒。

墙上再也没有啤酒瓶了。

将一个瓶子放入回收站,1 个空瓶子放入垃圾箱。

将一个瓶子放入回收箱,2个空瓶子放入垃圾箱。

将一个瓶子放入回收箱,3个空瓶子放入垃圾箱。

将一瓶放入回收箱,4个空瓶放入垃圾箱。

我非常了解第一部分,直到我谈到“墙上根本没有啤酒瓶了。我不明白瓶子的可变数量是如何从 1 增加到 4 的。

【问题讨论】:

    标签: c recursion procedural


    【解决方案1】:

    较小的啤酒瓶(及其相应的回收)处于内部功能。您的函数树如下所示:

    4 bottles of beer on the wall. 4 bottles of beer. Take one down, pass it around, 3 bottles of beer on the wall.
    |   3 bottles of beer on the wall. 3 bottles of beer. Take one down, pass it around, 2 bottles of beer on the wall.
    |   |   2 bottles of beer on the wall. 2 bottles of beer. Take one down, pass it around, 1 bottles of beer on the wall.
    |   |   |   1 bottles of beer on the wall. 1 bottles of beer. Take one down, pass it around, 0 bottles of beer on the wall.
    |   |   |   |   There are simply no more bottles of beer on the wall.
    |   |   |   Put a bottle in the recycling, 1 empty bottles in the bin.
    |   |   Put a bottle in the recycling, 2 empty bottles in the bin.
    |   Put a bottle in the recycling, 3 empty bottles in the bin.
    Put a bottle in the recycling, 4 empty bottles in the bin.
    

    【讨论】:

      【解决方案2】:

      在调试器中逐步了解 this is 函数所做的事情,您将确切了解此递归是如何工作的。我不是迂腐;我真的想不出比参考这种交互式方法更好的方法来说明这是做什么的。

      【讨论】:

      • 迭代可以提供帮助,这是一个很好的例子,但有时我认为从更高层次理解递归也更容易,尤其是当事情变得更加复杂时。例如,访问一棵树,或者特别是编写一个递归解析器,将其视为“tree.child IS a tree”使得代码更容易理解(在我看来),而不是迭代地遍历它。
      • @AdamD.Ruppe:但是在这种情况下,如果您逐步完成,则无需解释。
      【解决方案3】:

      一张简单的图说明:

      【讨论】:

        【解决方案4】:

        请注意,最后一个 printf 使用 numberOfBottles 变量,并且永远不会被修改。所以在打印oneFewer瓶返回后,它将打印带有numberOfBottles的回收文本。请记住,每次调用函数都有一个不同的局部变量化身。

        如果你缩进对函数的调用会更容易:

        4 bottles of beer on the wall...
          3 bottles of beer on the wall...
            2 bottles of beer on the wall...
              1 bottles of beer on the wall...
                There are simply no more bottles of beer on the wall.
              Put a bottle in the recycling, 1 empty bottles in the bin.
            Put a bottle in the recycling, 2 empty bottles in the bin.
          Put a bottle in the recycling, 3 empty bottles in the bin.
        Put a bottle in the recycling, 4 empty bottles in the bin.
        

        现在,从同一列开始的每一行都是从函数的同一次调用中写入的。你看瓶子的数量和回收率如何?那是因为两者都使用相同的变量:numberOfBottles

        【讨论】:

          【解决方案5】:

          回收语句在递归调用返回后运行。

          每个递归调用最终都会完成,程序将继续执行后面的回收语句,每个语句都有自己的局部变量值。

          【讨论】:

            【解决方案6】:

            使用递归,您可以将递归调用之前的所有内容视为前向循环,将调用之后的所有内容视为后向循环。 (尾递归——在函数结束时再次调用函数——通常被编译器优化为一个简单的前向循环。)

            它的工作方式是将每个旧函数的参数推入堆栈,并在全部返回时弹出。请记住,堆栈是后进先出的。因此,由于您从 4 开始,它会推送 4,然后是 3,然后是 2,然后是 1。当函数返回时,堆栈开始展开,因此您再次以相反的顺序看到参数:1,2,3,4。

            【讨论】:

            • 那么,递归的概念总是包含一个堆栈作为数据结构,还是只是一种解释后向循环机制的方式?堆栈非常清晰。这适用于任何编程语言吗?
            • 是的,它们将永远是一个堆栈(某种形式——优化可能会在某些情况下改变这一点))。当你调用另一个函数时,需要保存外层函数的状态,这样当内层函数结束时可以恢复。这是通过任何语言的堆栈完成的(同样,除非它得到不同的优化,但它仍然以相同的方式工作)。
            【解决方案7】:

            它以这种方式工作的原因是每次调用 numberOfBottles 大于 1 的 singSongFor() 将依次递归调用 singSongFor() 直到 numberOfBottles 为 0。此时到达 printf("There are simply no more bottles of beer on the wall.\n\n") 并且该函数将完成,传递给调用函数,该函数将传入一个参数 1,然后到达 printf("Put a bottle in the recycling, %d empty bottles in the bin.\n", numberOfBottles); 并自行完成,返回到 singSongFor(2)... 依此类推,直到你回到原来的号码, 在这种情况下为 4。

            【讨论】:

              猜你喜欢
              • 2016-10-06
              • 2020-04-03
              • 1970-01-01
              • 2014-08-03
              • 2021-03-22
              • 2021-04-27
              • 1970-01-01
              • 2014-12-09
              • 1970-01-01
              相关资源
              最近更新 更多