这里的递归是如何工作的?
代码 1:
public static int fibonacci (int n){
if (n == 0 || n == 1) {
return 1;
} else {
return fibonacci (n-1) + fibonacci (n-2);
}
}
如果您还没有解释完斐波那契的含义,那么如何使用斐波那契呢?我已经能够理解在其他情况下使用递归,如下所示:
代码 2:
class two
{
public static void two (int n)
{
if (n>0)
{
System.out.println (n) ;
two (n-1) ;
}
else
{
return ;
}
}
public static void main (String[] arg)
{
two (12) ;
}
}
但是,在代码 2 的情况下,n
最终将达到不满足 的点n>0
并且该方法将停止递归调用自身。不过,就代码 2 而言,如果 n=1
是 2、3 和 5 等的起点,我不知道它如何能够从 1 开始。另外,我不明白 return fibonacci (n-1) + fibonacci (n-2)
行如何工作,因为 fibonacci (n-2)
必须包含从某种意义上说,fibonacci (n-1)
才能发挥作用,但目前还没有。
我正在看的书说它会起作用。它是如何运作的?
Code 1:
public static int fibonacci (int n){
if (n == 0 || n == 1) {
return 1;
} else {
return fibonacci (n-1) + fibonacci (n-2);
}
}
How can you use fibonacci
if you haven't gotten done explaining what it is yet? I've been able to understand using recursion in other cases like this:
Code 2:
class two
{
public static void two (int n)
{
if (n>0)
{
System.out.println (n) ;
two (n-1) ;
}
else
{
return ;
}
}
public static void main (String[] arg)
{
two (12) ;
}
}
In the case of code 2, though, n
will eventually reach a point at which it doesn't satisfy n>0
and the method will stop calling itself recursively. In the case of code 2, though, I don't see how it would be able to get itself from 1 if n=1
was the starting point to 2 and 3 and 5 and so on. Also, I don't see how the line return fibonacci (n-1) + fibonacci (n-2)
would work since fibonacci (n-2)
has to contain in some sense fibonacci (n-1)
in order to work, but it isn't there yet.
The book I'm looking at says it will work. How does it work?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
好吧,抛开编译器实际上对代码做了什么(它很可怕,但很漂亮)以及 CPU 实际如何解释代码(同样),有一个相当简单的解决方案。
考虑这些文本说明:
要对编号块进行排序:
左侧的数字较低,
右边的数字更大。
当您看到说明 4 和 5 时,系统会要求您重新开始整个过程。然而,这不是问题,因为您仍然知道如何开始该过程,并且当一切最终解决时,您将获得一堆已排序的块。你可以用纸条盖住说明,遵循它们也不会更困难。
Well, putting aside what a compiler actually does to your code (it's horrible, yet beautiful) and what how a CPU actually interprets your code (likewise), there's a fairly simple solution.
Consider these text instructions:
To sort numbered blocks:
with lower numbers to the left side,
higher numbers to the right.
When you get to instructions 4 and 5, you are being asked to start the whole process over again. However, this isn't a problem, because you still know how to start the process, and when it all works out in the end, you've got a bunch of sorted blocks. You could cover the instructions with slips of paper and they wouldn't be any harder to follow.
以使其看起来相似,您可以替换条件
if (n == 0 || n == 1)
与if (n < 2)
我怀疑您想写:“因为斐波那契 n-1 在某种意义上必须包含斐波那契 n-2”
如果我是对的,那么您将从下面的示例中看到,实际上 fibonacci (n-2) 对于每个递归级别都会被调用两次 (fibonacci(1)在示例中):
1. 在当前步骤执行fibonacci (n-2)时
2. 在下一步执行斐波那契 ((n-1)-1) 时
(另请仔细查看Spike 的评论)
假设您调用fibonacci(3),则斐波那契的调用堆栈将如下所示:
(Veer 提供了更详细的解释)
请注意,相加fibonacci(n) 仅在所有较小参数的函数返回后才发生(即 fibonacci(n-1)、fibonacci(n-2) >... fibonacci(2), fibonacci(1), fibonacci(0))
查看 调用堆栈 对于更大的数字,您可以运行此代码。
to make it look similar you can replace condition
if (n == 0 || n == 1)
withif (n < 2)
I suspect you wanted to write: "since fibbonacci n-1 has to contain in some sense fibonacci n-2"
If I'm right, then you will see from the example below, that actually fibonacci (n-2) will be called twice for every recursion level (fibonacci(1) in the example):
1. when executing fibonacci (n-2) on the current step
2. when executing fibonacci ((n-1)-1) on the next step
(Also take a closer look at the Spike's comment)
Suppose you call fibonacci(3), then call stack for fibonacci will be like this:
(Veer provided more detailed explanation)
Note, that adding up in fibonacci(n) takes place only after all functions for smaller args return (i.e. fibonacci(n-1), fibonacci(n-2)... fibonacci(2), fibonacci(1), fibonacci(0))
To see what is going on with call stack for bigger numbers you could run this code.
诀窍在于,对 fibonacci() 的第一次调用只有在对 fibonacci() 的调用返回后才会返回。
你最终会在堆栈上调用一个又一个的 fibonacci() ,没有一个返回,直到你到达 n == 0 || 的基本情况。 n == 1。 此时,fibonacci() 调用的(可能很大)堆栈开始回退到第一个调用。
一旦你集中注意力,它就会变得很漂亮,直到你的堆栈溢出。
The trick is that the first call to fibonacci() doesn't return until its calls to fibonacci() have returned.
You end up with call after call to fibonacci() on the stack, none of which return, until you get to the base case of n == 0 || n == 1. At this point the (potentially huge) stack of fibonacci() calls starts to unwind back towards the first call.
Once you get your mind around it, it's kind of beautiful, until your stack overflows.
“如果你还没有解释完斐波那契是什么,你怎么能使用斐波那契呢?”
这是质疑递归的一种有趣的方式。以下是部分答案:当您定义斐波那契数时,它还没有被定义,但它已经被声明了。编译器知道有一个叫做 Fibonacci 的东西,并且它将是一个 int 类型的函数 -> int 并且它将在程序运行时被定义。
事实上,这就是 C 程序中所有标识符的工作方式,而不仅仅是递归标识符。编译器确定声明了哪些内容,然后遍历程序,将这些内容的使用指向这些内容的实际位置(严重过度简化)。
"How can you use Fibonacci if you haven't gotten done explaining what it is yet?"
This is an interesting way to question recursion. Here's part of an answer: While you're defining Fibonacci, it hasn't been defined yet, but it has been declared. The compiler knows that there is a thing called Fibonacci, and that it will be a function of type int -> int and that it will be defined whenever the program runs.
In fact, this is how all identifiers in C programs work, not just recursive ones. The compiler determines what things have been declared, and then goes through the program pointing uses of those things to where the things actually are (gross oversimplification).
让我演练考虑 n=3 的执行过程。希望有帮助。
当n=3时=> if 条件失败,else 执行
拆分语句:
// 请注意,它不是 fib(n-2),也不需要 fib(n-1) 来执行。它是独立的。这也适用于步骤 1。
执行方式(扩展上述四个步骤):
查找斐波那契(2)的值
查找 fibonacci(1) 的值
将两个值相加
Let me walkthrough the execution considering n=3. Hope it helps.
When n=3 => if condition fails and else executes
Split the statement:
// Note that it is not fib(n-2) and it is not going to require fib(n-1) for its execution. It is independent. This applies to step 1 also.
The way it gets executed(Expanding the above four steps):
Find the value of fibonacci(2)
Find the value of fibonacci(1)
Add both values
尝试自己画一个插图,你最终会看到它是如何工作的。请注意,当进行函数调用时,它将首先获取其
返回
值。简单的。Try to draw an illustration yourself, you will eventually see how it works. Just be clear that when a function call is made, it will fetch its
return
value first. Simple.尝试调试并使用监视来了解变量的状态
Try debugging and use watches to know the state of the variable
了解递归还需要了解调用堆栈的工作原理,即函数如何相互调用。
如果函数没有在 n==0 或 n==1 时停止的条件,则函数将永远递归地调用自身。
它之所以有效,是因为最终,该函数将逐渐消失并返回 1。此时,返回 fibonacci (n-1) + fibonacci (n-2) 也将返回一个值,并且调用堆栈实际上会被清理干净迅速地。
Understanding recursion requires also knowing how the call stack works i.e. how functions call each other.
If the function didn't have the condition to stop if n==0 or n==1, then the function would call itself recursively forever.
It works because eventually, the function is going to petter out and return 1. at that point, the return fibonacci (n-1) + fibonacci (n-2) will also return with a value, and the call stack gets cleaned up really quickly.
我将用一个例子来解释你的电脑在执行这段代码时正在做什么:
想象你站在一个非常大的房间里。在这个房间旁边的房间里有大量的纸、笔和桌子。现在我们要计算斐波那契(3):
我们拿一张桌子并将其放在房间的某个地方。我们在桌子上放一张纸,并在上面写上“n=3”。然后我们问自己“嗯,3 等于 0 还是 1?”。答案是否定的,所以我们会做“return fibonacci (n-1) + fibonacci (n-2);”。
然而有一个问题,我们不知道“斐波那契 (n-1)”和“斐波那契 (n-2)”实际上是做什么的。因此,我们再拿两张桌子,将它们放在原始桌子的左侧和右侧,并在两张桌子上贴上一张纸,上面写着“n=2”和“n=1”。
我们从左表开始,想知道“2 等于 0 还是 1?”。当然答案是否定的,所以我们再次在这个表旁边放置两个表,分别是“n=1”和“n=0”。
还在关注吗?这就是房间的样子:
n=1
n=2 n=3 n=1
n=0
我们从“n=1”的表开始,嘿,1 等于 1,所以我们实际上可以返回一些东西有用!我们在另一张纸上写下“1”,然后回到上面有“n=2”的表格。我们把纸放在桌子上,然后走到另一张桌子,因为我们仍然不知道要对另一张桌子做什么。
“n=0”当然也返回1,所以我们把它写在纸上,回到n=2表并将纸放在那里。此时,这张表上有两张纸,其返回值为“n=1”和“n=0”的表,所以我们可以计算出这个方法调用的结果实际上是2,所以我们把它写在纸上,放在桌子上,上面写着“n=3”。
然后我们一直向右走到上面有“n=1”的桌子,我们可以立即在纸上写下1,然后将其放回到上面有“n=3”的桌子上。之后,我们终于有足够的信息表明 fibonacci(3) 返回 3。
重要的是要知道您正在编写的代码只不过是一个菜谱。编译器所做的就是将该配方转换为您的 PC 可以理解的另一个配方。如果代码完全是伪造的,就像这样:
将简单地无限循环,或者像我的示例一样,您将继续放置越来越多的表,而实际上却没有任何有用的地方。您的编译器不关心 fibonacci(n-1) 或 fibonacci(n-2) 实际做什么。
I'll explain what your PC is doing when executing that piece of code with an example:
Imagine you're standing in a very big room. In the room next to this room you have massive amounts of paper, pens and tables. Now we're going to calculate fibonacci(3):
We take a table and put it somewhere in the room. On the table we place a paper and we write "n=3" on it. We then ask ourselves "hmm, is 3 equal to 0 or 1?". The answer is no, so we will do "return fibonacci (n-1) + fibonacci (n-2);".
There's a problem however, we have no idea what "fibonacci (n-1)" and "fibonacci (n-2)" actually do. Hence, we take two more tables and place them to the left and right of our original table with a paper on both of them, saying "n=2" and "n=1".
We start with the left table, and wonder "is 2 equal to 0 or 1?". Of course, the answer is no, so we will once again place two tables next to this table, with "n=1" and "n=0" on them.
Still following? This is what the room looks like:
n=1
n=2 n=3 n=1
n=0
We start with the table with "n=1", and hey, 1 is equal to 1, so we can actually return something useful! We write "1" on another paper and go back to the table with "n=2" on it. We place the paper on the table and go to the other table, because we still don't know what we're going to do with that other table.
"n=0" of course returns 1 as well, so we write that on a paper, go back to the n=2 table and put the paper there. At this point, there are two papers on this table with the return values of the tables with "n=1" and "n=0" on them, so we can compute that the result of this method call is actually 2, so we write it on a paper and put it on the table with "n=3" on it.
We then go to the table with "n=1" on it all the way to the right, and we can immediately write 1 on a paper and put it back on the table with "n=3" on it. After that, we finally have enough information to say that fibonacci(3) returns 3.
It's important to know that the code you are writing is nothing more than a recipe. All the compiler does is transform that recipe in another recipe your PC can understand. If the code is completely bogus, like this:
will simply loop endlessly, or as in my example, you'll keep on placing more and more tables without actually getting anywhere useful. Your compiler doesn't care what fibonacci(n-1) or fibonacci(n-2) actually do.