简体   繁体   English

这里的递归如何工作?

[英]How does the recursion here work?

Code 1: 代码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? 如果你还没有完成解释它是什么,你如何使用fibonacci I've been able to understand using recursion in other cases like this: 我已经能够理解在其他情况下使用递归,如下所示:

Code 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) ;
    }
}

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. 但是,在代码2的情况下, n最终将达到不满足n>0的点,并且该方法将停止递归调用自身。 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. 但是,在代码2的情况下,如果n=1是2和3和5的起点,我看不出它是如何从1中获得的,依此类推。 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. 此外,我没有看到线return fibonacci (n-1) + fibonacci (n-2)起作用,因为fibonacci (n-2)必须包含某种意义上的fibonacci (n-1)才能起作用,但它还没有。

The book I'm looking at says it will work. 我正在看的这本书说它会起作用。 How does it work? 它是如何工作的?

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. 好吧,撇开编译器实际对你的代码做的事情(它是可怕的,但是很漂亮)以及CPU如何实际解释你的代码(同样),这是一个相当简单的解决方案。

Consider these text instructions: 请考虑以下文字说明:

To sort numbered blocks: 要对编号的块进行排序

  1. pick a random block. 选一个随机区块。
  2. if it is the only block, stop. 如果它是唯一的阻止,停止。
  3. move the blocks with lower numbers to the left side, higher numbers to the right. 将数字较小的块移动到左侧,向右移动较大的数字。
  4. sort the lower-numbered blocks. 对较低编号的块进行排序。
  5. sort the higher-numbered blocks. 排序编号较高的块。

When you get to instructions 4 and 5, you are being asked to start the whole process over again. 当您收到说明4和5时,系统会要求您重新开始整个过程​​。 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. 你可以用纸条来说明这些说明,并且它们不会更难以遵循。

In the case of code 2 though n will eventualy reach a point at which it doesnt satisfy n>0 and the method will stop calling itself recursivly 在代码2的情况下,尽管n将最终达到它不满足n> 0的点,并且该方法将停止递归调用自身

to make it look similar you can replace condition if (n == 0 || n == 1) with if (n < 2) 为了使它看起来相似你可以用if (n < 2)替换条件if (n == 0 || n == 1) if (n < 2)

Also i don't see how the line `return fibonacci (n-1) + fibonacci (n-2) would work since fibbonacci n-2 has to contain in some sense fibonacci n-1 in order to wrok but it isn't there yet. 另外我不知道`返回fibonacci(n-1)+ fibonacci(n-2)是如何起作用的,因为fibbonacci n-2必须包含某种意义上的fibonacci n-1以便它不会发挥作用但它不是还有。

I suspect you wanted to write: " since fibbonacci n-1 has to contain in some sense fibonacci n-2 " 我怀疑你想写:“ 因为fibbonacci n-1必须包含某种意义上的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): 如果我是对的,那么你将从下面的例子中看到,实际上每个递归级别(示例中为fibonacci(1) 将调用fibonacci(n-2)两次:
1. when executing fibonacci (n-2) on the current step 1.在当前步骤中执行fibonacci(n-2)
2. when executing fibonacci ((n-1)-1) on the next step 2.在下一步执行斐波纳契((n-1)-1)

(Also take a closer look at the Spike's comment ) (另外仔细看看Spike的评论

Suppose you call fibonacci(3) , then call stack for fibonacci will be like this: 假设你调用fibonacci(3) ,那么为fibonacci 调用stack将是这样的:
(Veer provided more detailed explanation ) (Veer提供了更详细的解释

n=3. fibonacci(3)  
n=3. fibonacci(2) // call to fibonacci(n-1)
   n=2. fibonacci(1) // call to fibonacci(n-1)
      n=1. returns 1
   n=2. fibonacci(0) // call to fibonacci(n-2)
      n=0. returns 1
   n=2. add up, returns 2
n=3. fibonacci(1) //call to fibonacci(n-2)
   n=1. returns 1
n=3. add up, returns 2 + 1

Note, that adding up in fibonacci(n) takes place only after all functions for smaller args return (ie fibonacci(n-1) , fibonacci(n-2) ... fibonacci(2) , fibonacci(1) , fibonacci(0) ) 注意,只有在较小的args返回所有函数(即fibonacci(n-1)fibonacci(n-2) ... fibonacci(2)fibonacci(1)fibonacci之后才会在fibonacci(n)中加起来。 0)

To see what is going on with call stack for bigger numbers you could run this code. 要查看更大数字的调用堆栈的内容 ,您可以运行此代码。

public static String doIndent( int tabCount ){
    String one_tab = new String("   ");
    String result = new String("");
    for( int i=0; i < tabCount; ++i )
       result += one_tab;
    return result;
}

public static int fibonacci( int n, int recursion_level )
{
    String prefix = doIndent(recursion_level) + "n=" + n + ". ";

    if (n == 0 || n == 1){
        System.out.println( prefix + "bottommost level, returning 1" );
        return 1;
    }
    else{
        System.out.println( prefix + "left fibonacci(" + (n-1) + ")" );
        int n_1 = fibonacci( n-1, recursion_level + 1 );

        System.out.println( prefix + "right fibonacci(" + (n-2) + ")" );
        int n_2 = fibonacci( n-2, recursion_level + 1 );

        System.out.println( prefix + "returning " + (n_1 + n_2) );
        return n_1 + n_2;
    }
}

public static void main( String[] args )
{
    fibonacci(5, 0);
}

The trick is that the first call to fibonacci() doesn't return until its calls to fibonacci() have returned. 诀窍是,在对fibonacci()的调用返回之前,对fibonacci()的第一次调用才会返回。

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 || 最后调用堆栈上的fibonacci()后调用,没有返回,直到你得到n == 0 ||的基本情况 n == 1. At this point the (potentially huge) stack of fibonacci() calls starts to unwind back towards the first call. n == 1.此时(可能巨大的)fibonacci()调用堆栈开始向第一次调用展开。

Once you get your mind around it, it's kind of beautiful, until your stack overflows. 一旦你开始思考它,它会很漂亮,直到你的堆栈溢出。

"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. 编译器知道有一个名为Fibonacci的东西,它将是int - > int类型的函数,并且只要程序运行就会定义它。

In fact, this is how all identifiers in C programs work, not just recursive ones. 事实上,这就是C程序中所有标识符的工作方式,而不仅仅是递归标识符。 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). 编译器确定已声明的内容, 然后通过程序将这些内容的使用指向事物的实际位置(粗略过度简化)。

Let me walkthrough the execution considering n=3. 考虑到n = 3,让我演练执行。 Hope it helps. 希望能帮助到你。

When n=3 => if condition fails and else executes 当n = 3 =>如果条件失败,则执行

return fibonacci (2) + fibonacci (1);  

Split the statement: 拆分声明:

  1. Find the value of fibonacci(2) 找到斐波纳契的价值(2)
  2. Find the value of fibonacci(1) 找到斐波纳契的价值(1)
    // Note that it is not fib(n-2) and it is not going to require fib(n-1) for its execution. //注意它不是fib(n-2)并且它不需要fib(n-1)来执行它。 It is independent. 它是独立的。 This applies to step 1 also. 这也适用于步骤1。
  3. Add both values 添加两个值
  4. return the summed up value 返回总计的值

The way it gets executed(Expanding the above four steps): 它的执行方式(扩展以上四个步骤):

  1. Find the value of fibonacci(2) 找到斐波纳契的价值(2)

    1. if fails, else executes 如果失败,否则执行
    2. fibonacci(1) 斐波纳契(1)
      1. if executes 如果执行
      2. value '1' is returned to step 1.2. 值“1”返回到步骤1.2。 and the control goes to step 1.3. 并且控制转到步骤1.3。
    3. fibonacci(0) 斐波纳契(0)
      1. if executes 如果执行
      2. value '1' is returned to step 1.3. 值“1”返回到步骤1.3。 and the control goes to step 1.4. 并且控制转到步骤1.4。
    4. Add both 添加两者
      1. sum=1+1=2 //from steps 1.2.2. sum = 1 + 1 = 2 //来自步骤1.2.2。 and 1.3.2. 和1.3.2。
    5. return sum // value '2' is returned to step 1. and the control goes to step 2 返回值//值“2”返回到步骤1.控制转到步骤2
  2. Find the value of fibonacci(1) 找到斐波纳契的价值(1)

    1. if executes 如果执行
    2. value '1' is returned 返回值“1”
  3. Add both values 添加两个值

    1. sum=2+1 //from steps 1.5. sum = 2 + 1 //来自步骤1.5。 and 2.2. 和2.2。
  4. return the summed up value //sum=3 返回总和值// sum = 3

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. 需要明确的是,当进行函数调用时,它将首先获取其return值。 Simple. 简单。

尝试调试并使用watch来了解变量的状态

Understanding recursion requires also knowing how the call stack works ie 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. 如果函数没有条件在n == 0或n == 1时停止,则函数将永远递归地调用自身。 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. 它起作用,因为最终,函数将逐渐退出并返回1.此时,返回的fibonacci(n-1)+ fibonacci(n-2)也将返回一个值,并且调用栈真的被清理掉了很快。

I'll explain what your PC is doing when executing that piece of code with an example: 我将用一个例子来解释你的PC在执行那段代码时所做的事情:

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): 现在我们要计算斐波那契(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. 在桌子上我们放了一张纸,上面写着“n = 3”。 We then ask ourselves "hmm, is 3 equal to 0 or 1?". 然后我们问自己“嗯,3等于0还是1?”。 The answer is no, so we will do "return fibonacci (n-1) + fibonacci (n-2);". 答案是否定的,所以我们将做“返回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. 然而,有一个问题,我们不知道“斐波那契(n-1)”和“斐波纳契(n-2)”究竟是做什么的。 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". 因此,我们再拿两张桌子,将它们放在我们原始桌子的左侧和右侧,两张纸上都有一张纸,上面写着“n = 2”和“n = 1”。

We start with the left table, and wonder "is 2 equal to 0 or 1?". 我们从左表开始,并想知道“2是等于0还是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. 当然,答案是否定的,所以我们将再次在此表旁边放置两个表,其中“n = 1”和“n = 0”。

Still following? 还在关注? This is what the room looks like: 这就是房间的样子:

n=1 n = 1的

n=2 n=3 n=1 n = 2 n = 3 n = 1

n=0 n = 0的

We start with the table with "n=1", and hey, 1 is equal to 1, so we can actually return something useful! 我们从“n = 1”的表开始,嘿,1等于1,所以我们实际上可以返回一些有用的东西! We write "1" on another paper and go back to the table with "n=2" on it. 我们在另一张纸上写“1”,然后回到表格,上面写着“n = 2”。 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. “n = 0”当然也会返回1,所以我们在纸上写下来,回到n = 2表并将纸张放在那里。 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. 此时,该表上有两篇论文,其中包含“n = 1”和“n = 0”的表的返回值,因此我们可以计算出此方法调用的结果实际为2,所以我们把它写在纸上,然后把它放在桌子上,上面写着“n = 3”。

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. 然后我们一直走到右边的“n = 1”表,我们可以立即在纸上写1,然后把它放回到桌子上,上面写着“n = 3”。 After that, we finally have enough information to say that fibonacci(3) returns 3. 在那之后,我们终于有足够的信息说斐波纳契(3)返回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. 所有编译器都会在您的PC可以理解的另一个配方中转换该配方。 If the code is completely bogus, like this: 如果代码完全是伪造的,像这样:

    public static int NotUseful()
    {
        return NotUseful();
    }

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. 你的编译器不关心fibonacci(n-1)或fibonacci(n-2)实际上做什么。

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

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