这里的递归如何工作?

时间:2010-03-09 05:14:43

标签: java recursion fibonacci

代码1:

public static int fibonacci (int n){ 
    if (n == 0 || n == 1) { 
        return 1; 
    } else { 
        return fibonacci (n-1) + fibonacci (n-2); 
    }        
} 

如果你还没有完成解释它是什么,你如何使用fibonacci?我已经能够理解在其他情况下使用递归:

代码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)以便工作,但它还没有。

我正在看的这本书说它会起作用。它是如何工作的?

9 个答案:

答案 0 :(得分:12)

好吧,把编译器实际对你的代码做的事情放在一边(这很糟糕,但很漂亮)以及CPU如何实际解释你的代码(同样),这是一个相当简单的解决方案。

请考虑以下文字说明:

要对编号的块进行排序:

  1. 选择一个随机区块。
  2. 如果是唯一的阻止,请停止。
  3. 移动块 左侧数字较小, 更高的数字在右边。
  4. 对编号较低的块进行排序。
  5. 对编号较高的块进行排序。
  6. 当您收到说明4和5时,系统会要求您重新开始整个过程​​。但是,这不是问题,因为你仍然知道如何启动这个过程,当它最终完成时,你会得到一堆排序的块。你可以用纸条来说明这些说明,并且它们不会更难以理解。

答案 1 :(得分:3)

  

在代码2的情况下,虽然n最终会达到不满足n> 0的点,并且该方法将停止递归调用自身

要使其看起来相似,您可以将条件if (n == 0 || n == 1)替换为if (n < 2)

  

此外我还没有看到`返回fibonacci(n-1)+ fibonacci(n-2)是如何工作的,因为fibbonacci n-2必须包含某种意义上的fibonacci n-1才能使它变得更好还没有。

我怀疑你想写:“因为fibbonacci n-1 必须包含某种意义上的fibonacci n-2
如果我是对的,那么你将从下面的例子中看到,实际上 fibonacci(n-2)将在每个递归级别被调用两次( fibonacci(1)在例子中):
1.在当前步骤执行 fibonacci(n-2)时 2.在下一步执行 fibonacci((n-1)-1)

(另请仔细查看Spike's comment

假设你打电话给 fibonacci(3),那么 fibonacci call stack将是这样的:
(Veer提供more detailed explanation

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

注意,只有在较小args的所有函数返回后才会在 fibonacci(n)中相加(即 fibonacci(n-1) fibonacci (n-2) ... 斐波那契(2)斐波那契(1)斐波那契(0)

要查看call stack更大号码的内容,您可以运行此代码。

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

答案 2 :(得分:2)

诀窍是,在对fibonacci()的调用返回之前,对fibonacci()的第一次调用才会返回。

你最终调用堆栈上的fibonacci()调用,没有返回,直到你到达n == 0 ||的基本情况n == 1.此时,(可能很大的)fibonacci()调用堆栈开始向第一次调用展开。

一旦你开始思考它,它会很漂亮,直到你的堆栈溢出。

答案 3 :(得分:2)

“如果你还没有完成对Fibonacci的解释,你怎么能使用Fibonacci?”

这是一种质疑递归的有趣方法。这是答案的一部分:当你定义Fibonacci时,尚未定义尚未,但它已被声明。编译器知道有一个名为Fibonacci的东西,它将是int - &gt;类型的函数。 int,并且只要程序运行就定义。

实际上,这就是C程序中所有标识符的工作方式,而不仅仅是递归标识符。编译器确定已声明的内容,然后通过程序将这些内容的使用指向事物的实际位置(粗略过度简化)。

答案 4 :(得分:2)

让我在考虑n = 3的情况下完成执行。希望它有所帮助。

当n = 3 =&gt;如果条件失败,则执行

return fibonacci (2) + fibonacci (1);  

拆分声明:

  1. 找到斐波那契(2)
  2. 的价值
  3. 找出斐波那契(1)的价值 //注意它不是fib(n-2)并且它不需要fib(n-1)来执行它。它是独立的。这也适用于第1步。
  4. 添加两个值
  5. 返回总结值
  6. 执行方式(扩展以上四个步骤):

    1. 找到斐波那契(2)的价值

      1. 如果失败,则执行
      2. 斐波纳契(1)
        1. 如果执行
        2. 值'1'返回到步骤1.2。控制转到步骤1.3。
      3. 斐波纳契(0)
        1. 如果执行
        2. 值'1'返回到步骤1.3。控制转到步骤1.4。
      4. 同时添加两者
        1. sum = 1 + 1 = 2 //来自步骤1.2.2。和1.3.2。
      5. 返回总和//值“2”返回到步骤1.控制转到步骤2
    2. 找到斐波那契(1)的价值

      1. 如果执行
      2. 返回值“1”
    3. 添加两个值

      1. sum = 2 + 1 //来自步骤1.5。和2.2。
    4. 返回总和值// sum = 3

答案 5 :(得分:1)

尝试自己绘制插图,最终会看到它是如何工作的。需要明确的是,当进行函数调用时,它将首先获取其return值。简单。

答案 6 :(得分:1)

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

答案 7 :(得分:1)

理解递归还需要了解调用堆栈的工作原理,即函数如何相互调用 如果函数没有条件在n == 0或n == 1时停止,则函数将永远递归地调用自身。 它起作用,因为最终,函数将逐渐退出并返回1.此时,返回的fibonacci(n-1)+ fibonacci(n-2)也将返回一个值,并且调用栈真的被清理掉了快。

答案 8 :(得分:0)

我将用一个例子解释你的PC在执行这段代码时所做的事情:

想象一下,你站在一个非常大的房间里。在这个房间旁边的房间里,你有大量的纸张,钢笔和桌子。现在我们要计算斐波那契(3):

我们拿一张桌子把它放在房间的某个地方。在桌子上我们放了一张纸,上面写着“n = 3”。然后我们问自己“嗯,3等于0还是1?”。答案是否定的,所以我们将做“返回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”。在那之后,我们终于有足够的信息说斐波那契(3)返回3。


重要的是要知道你所编写的代码只不过是一个配方。所有编译器都会在您的PC可以理解的另一个配方中转换该配方。如果代码完全是虚假的,就像这样:

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

将无休止地循环,或者在我的示例中,您将继续放置越来越多的表而不会实际获得任何有用的表。你的编译器不在乎fibonacci(n-1)或fibonacci(n-2)实际上做什么。