【问题标题】:How should you approach recursion?你应该如何处理递归?
【发布时间】:2012-04-11 13:57:39
【问题描述】:

我目前正在学校学习递归,当有很多递归调用时,我很难思考方法。我只想问你应该如何考虑递归,因为我知道跟踪每一步的方法调用会变得太乏味。

我们简要介绍的不是跟踪每个递归调用,而是考虑通过归纳进行递归,但我遇到的问题是如何将归纳应用于数学以外的情况。就像如果有一种方法可以递归打印出这样的数字:

public void blah(int n)
{
    for (int i = 0; i < n; i++)
      blah(i);
    System.out.print(n);
}

我很难考虑打印出来的内容,而且我看不出这里的归纳有什么意义(如果它可以在任何地方使用,请原谅我的无知)。

但我想我真正的问题是如何在不必跟踪每个方法调用的情况下解决递归问题?最好的办法是查看基本情况和倒退的工作吗? (但即便如此,我认为我对发生的事情感到模糊)。

【问题讨论】:

标签: java recursion


【解决方案1】:

你可以在here上找到关于递归思考的很好的解释

来自链接

  • 为递归函数编写原型。

  • 写一个注释来描述函数的作用。

  • 确定基本情况(可能不止一种),及其 解决方案。

  • 确定要解决的小问题。如果它成功了 让您更容易理解,将解决方案保存到较小的位置
    局部变量的问题(例如 sum() 示例中的小问题).ASSUME 递归调用有效

  • 使用较小问题的解决方案来解决较大问题。 (如果这样做不正确,较小的解决方案
    问题也将被错误计算,因此,
    中的假设 上一步将失败)。

【讨论】:

【解决方案2】:

如何在不必跟踪每个方法调用的情况下解决递归问题?

有几种“理解”递归程序的方法 - 一种涉及将递归调用视为黑盒,另一种需要“播放”一些案例并猜测模式。

第一种方法假设递归方法已经编写好了,并且它做了一些已知的事情。当您想到递归下降解析器时,这很有用;这对于产生输出(相对于消费输入)的程序来说不是很好,比如你的。

第二种方法更适用于与您的示例类似的程序。播放值 0、1、2 和 3。

0 - 0
1 - 0 1
2 - 0 0 1 2
3 - 0 0 1 0 0 1 2 3

你注意到这个模式了吗? N 的输出列出了 N-1 先前项目的输出,并在末尾打印 N。一旦你认为你可以继续这个模式,你就知道你已经理解了你的递归程序。

【讨论】:

  • 感谢您提供有关寻找模式的见解。我从未在问题中注意到这一点,但考虑到递归的工作原理,它确实很有意义。最后,我想这也归结为多练习并掌握所有这些技巧。
  • @CowZow 欢迎您!如果您想更多地练习递归,请阅读 Dijkstra 的 Notes on Structured Programming 第 72..82 页:这本小书包含对各种编程技术(包括递归)的见解。
  • 喂,很棒的链接!我自己其实也是一个棋手,所以关于八皇后问题的那部分真的很有趣。再次感谢!
【解决方案3】:

这是我对递归的看法。其实很简单。

  • 我需要做什么才能停止?这就是您的基本情况。
  • 如果我还没有完成怎么办?

以经典递归问题为例:F(n) = n!。 0!定义为1,其他大于0的都定义为n*F(n-1)

在这个例子中,我满足了我的两个条件 - 当我达到 0 时停止!,我将我拥有的任何值乘以第 (n-1) 个值。

我使用的另一种方法:如果它可以递归完成,那么它可以迭代完成。也就是说,如果我可以编写一个循环来执行相同的任务,那么我也可以编写一个递归方法来执行该任务。递归思考某些问题(例如Ackermann's Function)和迭代思考其他问题(例如遍历链表)通常更容易。

您可能希望在最适合您的地方使用迭代。

【讨论】:

  • 我需要什么才能停止 - 很好。没有这种解释,“基本情况”并不明显。
【解决方案4】:

不知道如何确切地告诉你去想它,但我认为这可能有助于你掌握流程的样子。编写一段时间后你会感觉到它,但很多人只是避免它,因为它让他们感到恶心。我很抱歉它不是用 Java 编码的,但它不是关于代码,而是关于它打印的内容,所以只需在任何 Linux 机器或 cygwin 上运行它。

 perl -e 'sub foo {my $n =shift;my $depth=shift;print "\t"x$depth;print "blah($n)\n";for(my $i=0;$i<$n;$i++){foo($i,$depth+1)};print "\t"x$depth;print $n,"\n"};foo(shift);'  5

以 3 的 arg 调用,如下所示:

    blah(3)
            blah(0)
            0
            blah(1)
                    blah(0)
                    0
            1
            blah(2)
                    blah(0)
                    0
                    blah(1)
                            blah(0)
                            0
                    1
            2
    3

我尝试像其他人所说的那样将其可视化为更小的组件。 IE。除了递归之外,函数在做什么。在这种情况下,函数从 0 计数到某个数字。现在考虑递归的作用。对于每个计数,它都会开始一个新的计数,直到它达到的数量。我经常发现它有助于将其分解为多个函数,以便封装它真正做的事情,并将递归分开,但这使得递归不那么明显。

我认为它也有助于使用现实世界的问题,例如遍历目录层次结构或其他树结构。

【讨论】:

    【解决方案5】:

    你的例子会打印出来

    0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 4
    

    如果用 blah(4) 调用。

    一般来说,在递归时,我会确保首先处理基本情况。之后,处理递归的状态,然后逻辑就可以来了。

    在这个例子中,基本情况控制是i &lt; n,它将首先出现在0 &lt; 0,这是不真实的,并且会中断for循环打印输出0。然后下一个迭代将运行,即从@987654324开始@ 在调用 i = 0

    【讨论】:

      【解决方案6】:

      所以我会在这里直言不讳,你要么得到递归,要么你不会,这绝对没有错。这只是区分程序员的事情之一(sad but true according to Joel)。现在像你五岁一样解释递归是任务变得有点模糊的地方。想象一下,您有四 (4) 个苹果,每次我让您数一数时,您都会拿一个苹果给我。我第一次和你说话时,你会告诉我你有四个苹果,然后递给我一个。现在我们继续这个过程,直到你有零个苹果,这类似于其他人所说的 base caseexit statement 确保你的函数将终止。

      现在,你不再是 5 岁了,如果我要求你证明对于所有 N 实例,这将有效,你会怎么做?这就是你的教授说通过归纳来解决问题时的意思。以下是如何通过归纳求解的示例:我的办公桌上有六罐激浪,我正在喝其中一罐。我说:“哇,这罐激浪的味道就像一道彩虹。”我会用归纳法说其他五罐以及所有的激浪罐都尝起来像电子彩虹。因此,在递归的情况下,您证明一个函数将使用相同的进程终止并且是正确的。

      它可能有助于解决问题的“微不足道”实例,例如blah(0) and blah(1) and blah(2),这将向您展示解决方案朝着您预期的方向发展,以及您可以使用归纳法来说明此过程将终止的事实给定任何输入N

      【讨论】:

        猜你喜欢
        • 2014-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-26
        • 2012-05-02
        • 2017-03-26
        • 1970-01-01
        相关资源
        最近更新 更多