简体   繁体   English

你应该如何处理递归?

[英]How should you approach recursion?

I am currently studying recursion in school, and I have trouble thinking about methods when there are many recursive calls. 我目前在学校学习递归,当有很多递归调用时,我很难考虑方法。 I just want to ask how you should think about recursion because I know tracing the method calls by each step will become too tedious. 我只想问你应该如何考虑递归,因为我知道每一步跟踪方法调用都会变得太乏味。

Instead of tracing each recursive call, the thing we briefly covered was thinking about recursion by induction, but the problem I have is seeing how induction can be applied to situations other than math. 我们简要介绍的不是跟踪每个递归调用,而是考虑通过归纳进行递归,但我遇到的问题是如何将归纳法应用于数学以外的情境。 Like if there is a method that recursively prints out numbers like this: 就像有一个方法递归打印出这样的数字:

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

I have trouble thinking about what prints out, and I can't see how induction could be relevant here (pardon my ignorance if it can be used everywhere). 我无法考虑打印出来的内容,我无法看到感应在这里是如何相关的(原谅我的无知,如果它可以在任何地方使用)。

But I guess my real question is how you can tackle recursion without having to trace every single method call? 但我想我真正的问题是如何在不必跟踪每个方法调用的情况下解决递归问题? Is the best thing to do just to see the base case and sort of work backwards? 最好的做法只是看看基本案例和倒退工作吗? (But even then I think I get fuzzy about what happens). (但即便如此,我认为我对发生的事情感到模糊)。

You can find a nice explanation about Thinking Recursively over here 你可以在这里找到关于Thinking Recursively的一个很好的解释

From the link 从链接

  • Write a prototype for the recursive function. 编写递归函数的原型。

  • Write a comment that describes what the function does. 写一个描述函数功能的注释。

  • Determine the base case (there may be more than one), and its solution(s). 确定基本情况(可能有多个)及其解决方案。

  • Determine what smaller problem (or problems) to solve. 确定要解决的较小问题(或问题)。 If it makes it easier for you to follow, save the solutions to the smaller 如果它使您更容易遵循,请将解决方案保存到较小的
    problems to local variables (eg, small in the sum() example).ASSUME the recursive call works 局部变量的问题(例如,sum()示例中的小)。递归调用有效.ASSUME

  • Use the solutions of the smaller problem to solve the larger problem. 使用较小问题的解决方案来解决更大的问题。 (If this is done INCORRECTLY, the solutions of the smaller (如果这样做不正确,那么较小的解决方案
    problems will also be computed incorrectly, thus, the assumption in 问题也将被错误计算,因此,假设在
    the previous step will fail). 上一步将失败)。

how you can tackle recursion without having to trace every single method call? 如何在不必跟踪每个方法调用的情况下处理递归?

There are several ways of "understanding" recursive programs - one involves thinking of a recursive call as a black box, and the other requires "playing out" a few cases and guessing the pattern. 有几种方法可以“理解”递归程序 - 一种是将递归调用视为黑盒子,另一种需要“播放”一些案例并猜测模式。

The first method assumes that the recursive method is already written, and that it does some known thing. 第一种方法假定已经编写了递归方法,并且它做了一些已知的事情。 This is useful when you think of recursive descent parsers; 当你想到递归下降解析器时,这很有用; it is not that good for programs that produce output (as opposed to consuming input), such as yours. 对于产生输出(而不是消耗输入)的程序,例如你的程序,它并不是那么好。

The second method is more applicable to programs similar to your example. 第二种方法更适用于与您的示例类似的程序。 Play it out for values 0, 1, 2, and 3. 播放值0,1,2和3。

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

Did you notice the pattern? 你注意到了这种模式吗? The output for N lists outputs for N-1 prior items, and prints N at the end. N的输出列出N-1先前项目的输出,并在结尾打印N Once you think that you can continue the pattern, you know that you have an understanding of your recursive program. 一旦您认为可以继续使用该模式,就会知道您对递归程序有所了解。

Here's the way I think about recursion. 这是我考虑递归的方式。 It's pretty simple, actually. 实际上,它非常简单。

  • What do I need to do in order to stop? 我需要做什么才能停下来? This is known as your base case. 这被称为您的基本案例。
  • What do I do if I'm not done yet? 如果我还没完成怎么办?

Take, for example, the classical recursive problem: F(n) = n!. 举例来说,经典的递归问题:F(n)= n!。 0! 0! is defined to be 1, and anything else greater than 0 is defined to be n*F(n-1) 定义为1,其他任何大于0的定义为n * F(n-1)

In this example, I meet my two conditions - I stop when I hit 0!, and I multiply whatever value I have by the (n-1)th value. 在这个例子中,我满足了我的两个条件 - 当我达到0时我停止!我将任何值乘以第(n-1)个值。

Another approach I use: if it can be done recursively, then it can be done iteratively. 我使用的另一种方法是:如果可以递归完成,那么可以迭代完成。 This is to say, if I can write a loop to perform the same task, then I can write a recursive method to perform the task as well. 这就是说,如果我可以编写一个循环来执行相同的任务,那么我也可以编写一个递归方法来执行该任务。 It is often easier to think of certain problems recursively (eg Ackermann's Function ), and others iteratively (eg walking down a linked list). 通常可以更容易地递归地思考某些问题(例如Ackermann函数 ),而其他问题则是迭代地(例如,沿着链表向下走)。

You would want to use iteration where it suits you best. 您可能希望在最适合您的地方使用迭代

Your example will print out 您的示例将打印出来

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

If called with blah(4). 如果用blah(4)调用。

In general when recursing I make sure to handle the base case first. 通常,在递归时,我确保首先处理基本情况。 After that, handle the state of the recursion, then the logic can come. 之后,处理递归的状态,然后逻辑可以来。

In this example, the base case control is i < n and will occur first at 0 < 0 which is untrue and breaks the for loop printing out 0. Then the next iteration out will run, which was to go from i = 0 to 1 < 1 which again prints out 0 after calling i = 0 < 0. Then it finishes the loop and the 1 is printed. 在这个例子中,基本案例控制是i < n并且将首先出现在0 < 0这是不真实的并且打破for循环打印0.然后下一次迭代将运行,这将从i = 0 to 1 < 1 ,在调用i = 0 <0后再次打印出0然后它完成循环并打印1。 Then it is 2s turn, lol. 然后是2s转,哈哈。 And so on down the line back out until each number has had it's turn. 等等,直到每个号码轮到它为止。

Not sure exactly how to tell you to think of it but I think this might help you to grasp what the flow looks like. 不确定如何告诉你想到它但我认为这可能会帮助你掌握流动的样子。 You get a feel for it after you code it for a while, but a lot of people just avoid it because it makes them feel icky. 在你编码一段时间后,你会感觉到它,但很多人只是避免它,因为它让他们觉得很蠢。 I apologize that it isnt coded in Java, but its not about the code its about what it prints so just run it on any Linux box or cygwin. 我很抱歉它不是用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

Called with an arg of 3 this is what it looks like: 以arg为3调用,它的外观如下:

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

I try like someone else said to visualize it in smaller components. 我试着像其他人说要用更小的组件来形象化它。 IE What is the function doing besides the recursion. IE除了递归之外还有什么功能。 In this case the function is counting from 0 to some number. 在这种情况下,函数从0计数到某个数字。 Now factor in what the recursion does. 现在考虑递归的作用。 For each count it starts a new count up to the number it has reached. 对于每个计数,它开始一个新的计数,直到达到它的数量。 Often I find that it helps to break it up into more than one function such that the what it really does is encapsulated, and the recursion separate, but this makes the recursion less obvious. 我经常发现将它分解为多个函数是有帮助的,这样它实际上做的就是封装,并且递归分离,但这使得递归不那么明显。

I think it also helps to use real world problems, such as traversing a directory hierarchy or other tree structure. 我认为它也有助于使用现实世界的问题,例如遍历目录层次结构或其他树结构。

So I will be the blunt one here, you will either get recursion or you wont and there is absolutely nothing wrong with that. 所以我会在这里变钝,你要么得到递归,要么你不会,这绝对没有错。 It is just one of those things that separates programmers ( sad but true according to Joel ). 这只是将程序员分开的事情之一( 悲伤但根据乔尔的说法是真实的 )。 Now to explain recursion like you are five is where the task becomes a little murky. 现在解释递归就像你是五个是任务变得有点模糊的地方。 Imagine you have four (4) apples and every time I ask you to count them you take one of your apples and give it to me. 想象一下,你有四(4)个苹果,每次我要求你数一下,你拿一个苹果送给我。 The first time I talk to you you will tell me you have four apples and hand one to me. 我第一次和你说话时你会告诉我你有四个苹果并且给我一个。 Now we continue this process until you have zero apples this will be analogous to what others have called the base case or exit statement which ensures that your function will terminate. 现在我们继续这个过程,直到你有零苹果,这将类似于其他人所谓的base caseexit statement ,这将确保你的函数将终止。

Now, you are no longer five, if I asked you to prove that for all instances of N that this would work how would you do that? 现在,你不再是五岁了,如果我要求你证明对于N所有实例,这会有用吗你会怎么做? This is what your professor is getting at when he states to solve a problem via induction. 这是你的教授在通过归纳解决问题时所得到的。 An example of how to solve by induction would be the following: I have six cans of Mountain Dew on my desk and I am in the process of drinking one of them. 如何通过归纳解决的一个例子如下:我的桌子上有六罐山露,我正在喝其中一罐。 I say "Wow this can of Mountain Dew tastes like an electric rainbow." 我说“哇,山露的味道就像电彩虹一样。” I would use induction to say that the other five cans and by extension all cans of Mountain Dew taste like electric rainbows. 我会用感应来说其他五罐和延伸所有山露的罐子都像电动彩虹一样。 So in the case of recursion, you prove that a function will both terminate and be correct utilizing the same process. 因此,在递归的情况下,您证明函数将使用相同的过程终止并正确。

It may help to solve a "trivial" instance of the problem such as blah(0) and blah(1) and blah(2) this will show you that the solution trends in the direction that you anticipate as well as the fact you can use induction to say this process will terminate given any input N . 它可能有助于解决问题的“琐碎”实例,例如blah(0) and blah(1) and blah(2)这将向您显示解决方案趋向于您预期的方向以及您可以实现的事实使用归纳来说这个过程将在给定任何输入N终止。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM