簡體   English   中英

這里的遞歸如何工作?

[英]How does the recursion here work?

代碼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)才能起作用,但它還沒有。

我正在看的這本書說它會起作用。 它是如何工作的?

好吧,撇開編譯器實際對你的代碼做的事情(它是可怕的,但是很漂亮)以及CPU如何實際解釋你的代碼(同樣),這是一個相當簡單的解決方案。

請考慮以下文字說明:

要對編號的塊進行排序

  1. 選一個隨機區塊。
  2. 如果它是唯一的阻止,停止。
  3. 將數字較小的塊移動到左側,向右移動較大的數字。
  4. 對較低編號的塊進行排序。
  5. 排序編號較高的塊。

當您收到說明4和5時,系統會要求您重新開始整個過程​​。 但是,這不是問題,因為你仍然知道如何啟動這個過程,當它最終完成時,你會得到一堆排序的塊。 你可以用紙條來說明這些說明,並且它們不會更難以遵循。

在代碼2的情況下,盡管n將最終達到它不滿足n> 0的點,並且該方法將停止遞歸調用自身

為了使它看起來相似你可以用if (n < 2)替換條件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(1) 將調用fibonacci(n-2)兩次:
1.在當前步驟中執行fibonacci(n-2)
2.在下一步執行斐波納契((n-1)-1)

(另外仔細看看Spike的評論

假設你調用fibonacci(3) ,那么為fibonacci 調用stack將是這樣的:
(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

注意,只有在較小的args返回所有函數(即fibonacci(n-1)fibonacci(n-2) ... fibonacci(2)fibonacci(1)fibonacci之后才會在fibonacci(n)中加起來。 0)

要查看更大數字的調用堆棧的內容 ,您可以運行此代碼。

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

訣竅是,在對fibonacci()的調用返回之前,對fibonacci()的第一次調用才會返回。

最后調用堆棧上的fibonacci()后調用,沒有返回,直到你得到n == 0 ||的基本情況 n == 1.此時(可能巨大的)fibonacci()調用堆棧開始向第一次調用展開。

一旦你開始思考它,它會很漂亮,直到你的堆棧溢出。

“如果你還沒有完成它的解釋,你如何使用斐波那契?”

這是一種有趣的遞歸問題。 這里有一個答案的一部分:當你定義斐波那契數,它尚未定義,但它已被宣布 編譯器知道有一個名為Fibonacci的東西,它將是int - > int類型的函數,並且只要程序運行就會定義它。

事實上,這就是C程序中所有標識符的工作方式,而不僅僅是遞歸標識符。 編譯器確定已聲明的內容, 然后通過程序將這些內容的使用指向事物的實際位置(粗略過度簡化)。

考慮到n = 3,讓我演練執行。 希望能幫助到你。

當n = 3 =>如果條件失敗,則執行

return fibonacci (2) + fibonacci (1);  

拆分聲明:

  1. 找到斐波納契的價值(2)
  2. 找到斐波納契的價值(1)
    //注意它不是fib(n-2)並且它不需要fib(n-1)來執行它。 它是獨立的。 這也適用於步驟1。
  3. 添加兩個值
  4. 返回總計的值

它的執行方式(擴展以上四個步驟):

  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

嘗試自己畫一個插圖,你最終會看到它是如何工作的。 需要明確的是,當進行函數調用時,它將首先獲取其return值。 簡單。

嘗試調試並使用watch來了解變量的狀態

理解遞歸還需要了解調用堆棧的工作原理,即函數如何相互調用。
如果函數沒有條件在n == 0或n == 1時停止,則函數將永遠遞歸地調用自身。 它起作用,因為最終,函數將逐漸退出並返回1.此時,返回的fibonacci(n-1)+ fibonacci(n-2)也將返回一個值,並且調用棧真的被清理掉了很快。

我將用一個例子來解釋你的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)實際上做什么。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM