简体   繁体   English

递归在 Java 中实际上是如何工作的?

[英]How does Recursion actually work in Java?

I'm just new to this Recursion and I know at least that it is a technique by which a method calls itself during execution but I am quite confused on how it actually works.我只是这个递归的新手,我至少知道它是一种方法在执行过程中调用自身的技术,但我对它的实际工作方式感到非常困惑。

From the book at school, implementing the factorial function is the first example that was given.从学校的书中,实现阶乘函数是给出的第一个例子。

//int n = 5;
private static int factorial(int n) {
    if(n == 0) {
        return 1;
    }   
    else
        return n * factorial(n - 1);
}

I get how this factorial works somehow but I am not completely sure if I understood it correctly.我知道这个阶乘是如何工作的,但我不完全确定我是否正确理解它。 For every call the method refers to itself, n is multiplied to the factorial parameter n - 1 until it reaches 1 .对于该方法引用自身的每次调用, n乘以阶乘参数n - 1直到达到1

This is how I trace this recursion in my mind这就是我在脑海中追踪这个递归的方式

5 * factorial(5 - 1) = 20 //first call
20 * factorial(4 - 1) = 60 //second call
60 * factorial(3 - 1) = 120 //third call
120 * factorial(2 - 1) = still 120 // fourth call

120 is the last returned value;

And another example that was given is printing numbers from 1 to n using recursion instead of using the ordinary loop.给出的另一个例子是使用递归而不是使用普通循环打印从 1 到 n 的数字。

//int n = 10;
private static void printNumbers(int n) {
    if(n >= 1) {
        printNumbers(n - 1);
        System.out.print(n + " ");
    }
}

This outputs: 1 2 3 4 5 6 7 8 9 10这输出: 1 2 3 4 5 6 7 8 9 10

And now, the thing confuses me here is why the ouput starts at 1 ?现在,让我困惑的是为什么输出从1开始? Because from how I understand it the output should start at 9 up to 1 .因为根据我的理解,输出应该从9开始到1

Because from I understood it, at start the n = 10 and then 10 > 1 so of course it will call itself again then 10 - 1 then print 9 and then calls itself again and 9 - 1 then print 8 and so on...因为根据我的理解,在开始时n = 10然后10 > 1所以当然它会再次调用自己,然后10 - 1然后打印9然后再次调用自己,然后9 - 1然后打印8等等......

Can anyone please clarify this to me simply and correct my mistaken understanding to the examples I've mentioned, I am a little confused here and most of the post I can see here on StackOverflow about recursion is not really helping (But if there is a real good and clear answer about recursion in here that you know of, do give me the link).任何人都可以简单地向我澄清这一点,并纠正我对我提到的例子的错误理解,我在这里有点困惑,我在 StackOverflow 上看到的大部分关于递归的帖子都没有真正的帮助(但如果有你知道的关于递归的真正好的和明确的答案,请给我链接)。

Thank you...谢谢...

Because in fact nothing happens until the last call to factorial() returns 1 .因为实际上直到最后一次调用factorial()返回1时才会发生任何事情。 Internally, all intermediate results are "piled up" on the stack and then "piled down" when the function returns.在内部,所有中间结果都“堆积”在堆栈上,然后在函数返回时“堆积”。

So in fact, a call to factorial(3) does所以实际上,调用factorial(3)确实

factorial(3) = 3 * factorial(2) -->> 3 on the heap
factorial(2) = 2 * factorial(1) -->> 2,3 on the heap
factorial(1) = 1 * factorial(0) -->> 1,2,3 on the heap
factorial(0) = 1

Once the factorial(0) finally returns, all multiplications are rolled up:一旦factorial(0)最终返回,所有乘法都会累加:

1 * 1 * 2 * 3 = 6

Or written the other way:或者写成另一种方式:

factorial(0) = 1
factorial(1) = 1 * (1)
factorial(2) = 2 * (1 * (1))
factorial(3) = 3 * (2 * (1 * (1)))
factorial(4) = 4 * (3 * (2 * (1 * (1))))
...

Each opening parenthesis is a function call that pushes a value on the stack, and the multiplications can occur only when both left and right values are known, which is when it reaches 0 and returns 1 .每个左括号都是一个将值压入堆栈的函数调用,只有当左右值都已知时才能进行乘法运算,也就是当它达到0并返回1

Recursion is by no means an easy concept -- most people have trouble with it so you're not alone.递归绝不是一个简单的概念——大多数人都会遇到麻烦,所以你并不孤单。

Thinking about a problem recursively means that some computation gets repeated until the problem is solved (ie the base case is reached or the method is exhausted. For the Factorial, it is when n=0, for the number print, the method is exhausted [does nothing]).递归思考一个问题意味着重复一些计算直到问题解决(即达到基本情况或方法已用尽。对于阶乘,当n=0时,对于数字打印,方法已用尽[什么也没做])。 When this happens, the most recent computation is returned up the recursion level until there are no more levels left.发生这种情况时,最近的计算将返回递归级别,直到没有更多级别为止。 Thus, the problem is solved.这样,问题就解决了。

Here's an analogy: Think of this process as if you were mining.这是一个类比:把这个过程想象成你在挖矿。 The computation (or procedure) for mining (roughly) is to dig until you find what you want (let's say gems).挖掘(大致)的计算(或程序)是挖掘,直到找到你想要的东西(比如宝石)。 You start at the surface of the Earth.你从地球表面开始。 Are there gems?有宝石吗? This question is your base case-you will stop mining when you find your gems.这个问题是你的基本情况——当你找到你的宝石时,你将停止挖掘。 If there aren't gems, you must dig deeper (go down a level), so you call up the mining procedure.如果没有宝石,则必须深入挖掘(向下一层),然后调用挖掘程序。 You ask yourself again, are there gems?你再问自己,有宝石吗? If there aren't gems, you must dig deeper, etc. This will repeat, or recurse, until you can answer the question with 'Yes!如果没有宝石,您必须深入挖掘,等等。这将重复或递归,直到您可以用“是! I have found gems.'我发现了宝石。 Once the base case is reached, you are at the lowest level in the process, right?一旦达到基本情况,您就处于流程的最低级别,对吗? When mining, once you find the gems, you can't stay at the bottom of the mine.挖矿时,一旦找到宝石,就不能一直呆在矿井底部。 You must return to the top with the gems you have.你必须带着你拥有的宝石回到顶端。

That's recursion in a nutshell.简而言之,这就是递归。 Now to address your understanding:现在来解决您的理解:

You're correct with the conditional, 10 >= 1, so call printNumbers(10 - 1), and so on.您对条件 10 >= 1 是正确的,因此调用 printNumbers(10 - 1),依此类推。 However, it prints with 1 because that was the first thing returned.但是,它打印为 1,因为这是返回的第一件事。 If you look at the code, you'll notice that printNumbers(10 - 1) is called before System.out.println(n + " ");.如果您查看代码,您会注意到在 System.out.println(n + " "); 之前调用了 printNumbers(10 - 1)。 That means that before 10 is printed, printNumbers runs 9. When we get to printNumbers(1), this is what happens:这意味着在打印 10 之前,printNumbers 运行 9。当我们到达 printNumbers(1) 时,会发生以下情况:

  • Is one greater than or equal to one? 1 是大于还是等于 1? Yes, let's move on.是的,让我们继续。
  • printNumbers(1 - 1)打印数字(1 - 1)
  • Is zero greater than or equal to one?零是否大于或等于一? No, and since there's nothing after the conditional, the method is exhausted and returns.不,并且由于条件之后没有任何内容,因此该方法已用完并返回。
  • System.out.println(1 + " "); System.out.println(1 + " ");
  • Now printNumbers(1) is done, what's next?现在 printNumbers(1) 完成了,下一步是什么?
  • System.out.println(2 + " "); System.out.println(2 + " ");
  • Now printNumbers(2) is done, what's next?现在 printNumbers(2) 完成了,下一步是什么?
  • And so on, until printNumbers(10) - the first call of printNumbers依此类推,直到 printNumbers(10) - printNumbers 的第一次调用
  • System.out.println(10 + " "); System.out.println(10 + " ");
  • There is nothing else, problem solved!没有别的了,问题解决了!

That's all there is to it really.这就是全部。 If there's any confusion, let me know!如果有任何困惑,请告诉我!

I'm assuming this question is more about the order of the computations and not so much about understanding the idea of recursion.我假设这个问题更多地是关于计算的顺序,而不是关于理解递归的想法。

With recursion, things occur the other way around to what you have listed.通过递归,事情发生的方式与您列出的内容相反。 It works like a stack instead of a queue.它的工作原理类似于堆栈而不是队列。 If you've played card games like MTG, it works just like in the games.如果你玩过 MTG 之类的纸牌游戏,它就像在游戏中一样。 Last thing "activated" is done first. “激活”的最后一件事首先完成。

Basically, the first time you call the factorial function, it stalls at this point:基本上,第一次调用阶乘函数时,它会在此时停止:

return n * factorial(n - 1);

Because your program is now waiting for the result of factorial(n - 1) to proceed.因为您的程序现在正在等待factorial(n - 1)的结果继续进行。

It will keep calling subroutines (which, if big enough, can cause a stack overflow exception, what this site is named after).它将不断调用子程序(如果足够大,可能会导致堆栈溢出异常,这个站点就是以此命名的)。

Eventually, n will be equal to 0, and this branch will execute:最终,n 将等于 0,并且此分支将执行:

return 1;

So, at the top of your stack, you will have factorial(1) waiting for the result of factorial (0) .因此,在堆栈的顶部,您将有factorial(1)等待factorial (0)的结果。

return 1 * factorial(0); //come on hurry up factorial(0)!

Now with 0 being taken care of, 1 is ready to return it's value.现在处理 0 后,1 已准备好返回它的值。

return 1 * 1; //1 replaces factorial(0), now we can return!

Now we're at N = 2, still stuck at the same place.现在我们在 N = 2,仍然停留在同一个地方。

return 2 * factorial(1); //need factorial(1) to continue!

After factorial(1) returns we get:在 factorial(1) 返回后,我们得到:

return 2 * 1; //ready to return!

Then at N=3 (or factorial(3) )然后在 N=3 (或factorial(3)

return 3 * 2; //2 replaces factorial(2), now ready to return.

...and so on until ...依此类推,直到

return n * factorial(n - 1); //been waiting forever...

Now the other example:现在另一个例子:

//int n = 10;
private static void printNumbers(int n) {
    if(n >= 1) {
        printNumbers(n - 1);
        System.out.print(n + " ");
    }
}

The program will halt at printNumbers(n - 1);程序将在printNumbers(n - 1)处停止 in this case.在这种情况下。 It won't suddenly forget about the original call printNumbers(n) , but it needs to do printNumbers(n - 1) first.它不会突然忘记最初的调用printNumbers(n) ,但它需要先执行printNumbers(n - 1)

So starting with N = 10, it will say "okay to continue, I have to figure out what printNumbers(9) is. And then at N = 9, the same occurs with printNumbers(8) .所以从 N = 10 开始,它会说“好吧继续,我必须弄清楚printNumbers(9)是什么。然后在 N = 9 时,同样发生在printNumbers(8) 上

At N = 1, we have printNumbers(0) , which does nothing.在 N = 1 时,我们有printNumbers(0) ,它什么也不做。 So N = 1 proceeds to the next line and prints.所以 N = 1 继续下一行并打印。 Then returns back to the halted method N = 2, which was still waiting for printNumbers(1) to finish.然后返回到暂停的方法 N = 2,该方法仍在等待printNumbers(1)完成。 N = 2 prints and returns void, allowing N = 3 to continue and so on until N = 10. N = 2 打印并返回 void,允许 N = 3 继续,依此类推,直到 N = 10。

Take a look at this example which is different than yours but will help you understand how the order of operations affects recursion.看看这个例子,它与你的不同,但会帮助你理解操作顺序如何影响递归。 It works because of the order of operations on the line of the return.它之所以起作用,是因为返回行上的操作顺序。

public int getRSquare(int n){
    if(n==0){
        return 1;
    }
    return getRSquare(n-1)*n;
} 

You will see with each call of this method, the first thing to occour is the recursive call to getRSquare(n-1).您将看到每次调用此方法时,首先发生的是对 getRSquare(n-1) 的递归调用。 This happens before it multiplies what is returned.这发生在它乘以返回的内容之前。 The example below calls getSquare(3) then getSquare(2) then getSquare(1) then getSquare(0).下面的示例调用 getSquare(3) 然后 getSquare(2) 然后 getSquare(1) 然后 getSquare(0)。 It is only after this that the base case n==0 applies and no recursion happens, but a 1 is returned.只有在此之后,基本情况 n==0 才适用并且没有递归发生,但返回 1。 This returned value is then multiplied with the n of the previous call which is in many cases a different value.然后将此返回值与前一次调用的 n 相乘,这在许多情况下是不同的值。

result = getSquare( n=3);        
               getSquare(n=2)     
                      getSquare(n =1)     
                             getSquare(0)
                              Return a 1,
                         return returned value multiplied by n  (n is a 1 prev returned value is 1 = 1)
                  return returned value multiplied by n  (n is a 2 prev returned value is 1 = 2)
          return returned value multiplied by n  (n is a 3 prev returned value is 2 = 6)

You will see the above call to getSquare(3) will return a 6. Remember that n is local to each function call so each activation of the method getSquare will have its own n, as we recurse n gets smaller each time, as we return back up, n gets larger because that's the value it had at the previous method activation.您将看到上面对 getSquare(3) 的调用将返回 6。请记住,n 是每个函数调用的局部变量,因此 getSquare 方法的每次激活都将有自己的 n,因为我们每次递归 n 都会变小,因为我们返回备份,n 变大,因为这是它在前一个方法激活时的值。

As a practice task, I recommend is you use this approach so see what happens when you switch the order of operations such as:作为练习任务,我建议您使用这种方法,看看当您切换操作顺序时会发生什么,例如:

public int getRSquare(int n){
    if(n==0){
        return 1;
    }
    return n * getRSquare(n-1);
} 

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

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