Java結合方法棧幀理解遞歸編程思想

Java結合方法棧幀理解遞歸編程思想

遞歸的介紹

In computer programming, the term recursive describes a function or method that repeatedly calculates a smaller part of itself to arrive at the final result. It is similar to iteration, but instead of repeating a set of operations, a recursive function accomplishes repetition by referring to itself in its own definition. While the concept of recursive programming can be difficult to grasp initially, mastering it can be very useful. Recursion is one of the fundamental tools of computer science.

在計算機編程中,遞歸描述了一個函數或方法重複計算自身的更小部分單元,從而獲得最終結果。有點類似於迭代,但不是重複一系列的普通操作,而是在自身定義裏面重複調用自身完成。遞歸的概念確實比較難以理解,但是理解後是極其有用的。遞歸是計算機科學的工具之一。

上面是比較學術化的說法,關於遞歸,簡而言之——函數(或者某些語言叫方法)體裏面又調用了自身,從而得到最終的結果。

遞歸的注意事項

  • 一定要保證遞歸終止的條件,否則會陷入無限調用的噩夢
  • 每次遞歸,應該可以解決更小的子集問題

階乘——遞歸入門案例

階乘:是最好的遞歸案例。

0的階乘=1; ----- 因爲1!=1,根據1!=1*0!,所以0!=1而不是0。

1的階乘=1;

2的階乘=2*1!=2;

3的階乘=3*2!=6;

4的階乘=4*3!=24;

我們發現一個非負數的階乘 = 其值*(其值-1)!

在編程,求一個給定數的階時可以這麼實現:

private int factorial(int i){
    if( i <= 1 ){
        return 1;
    }else{
        return i * factorial(i-1);
    }
}

結果驗證…沒毛病!

遞歸和方法棧

回顧一下:JMM內存模型。

在這裏插入圖片描述

對於上面代碼:

private int factorial(int i){
    if( i <= 1 ){
        return 1;
    }else{
        return i * factorial(i-1);
    }
}

結合JMM模型來分析一下,每個方法調用時都有屬於自己的棧幀; 所以每次調用時都會

①保存當前這次棧幀的局部變量

②操作,去繼續調用比它小1的棧幀

③繼續執行①-③,知道找到最後一個——遞歸終止條件return 1

④方法逐步返回,回到上一層的棧幀…直到最開始的棧幀,拿到結果,出棧。

這個過程需要大量棧幀,我們知道棧幀是需要一定的內存的,所以空間損耗很大;

尾遞歸優化

尾遞歸——當遞歸調用時最後的語句是函數自身,並且沒有任何其他的表達式;

對於尾遞歸,現代編譯器會對其做優化,複用棧幀。

改寫,使用尾遞歸,複用棧幀:

private  int factorial2(int i, int result){
    if( i <= 1 ){
        return result;
    }else{
        return factorial2(i-1, i*result);
    }
}

最後的執行語句僅僅包含方法自身,則可以複用棧幀,只要一個棧幀即可。

漢諾塔實現

理解了遞歸思想後,來看一看當初數據結構課上的一個案例:漢諾塔。

在這裏插入圖片描述

對於初學者,這個案例看着很頭疼…似乎陷入了無解的狀態…

  • 圓盤一開始全部像疊羅漢一樣都在A,有空柱子B、C;
  • 最終要求全部放到C盤;
  • 盤移動過程中可以在任意柱子;
  • 一次只能移動一個盤;
  • 移動期間,需保證所有的柱子都是底層爲大盤,上面的爲小盤;

在這裏插入圖片描述

分析

這個問題如果存在解,那麼應該是採用遞歸、循環來實現;

遞歸? 如何遞歸?

如何拆分任務子集?

實現

在這裏插入圖片描述
假設A上面存在2個圓盤——要原樣移動到C,需要經歷以下步驟;

A移動到B

在這裏插入圖片描述

A移動到C

在這裏插入圖片描述
B移動到C

在這裏插入圖片描述

同樣的N個,可以把下面的N-1整體看成一個,最上面一個,合起來算做兩個;按上面的操作完成;

這就是不斷地細分,每一塊又是重複的動作,可以遞歸實現。

本質上就是一個入棧、出棧的過程

在這裏插入圖片描述

// 將 n 個圓盤從 a 經由 b 移動到 c 上
public void hanoid(int n, char a, char b, char c) {
    if (n <= 0) {
        return;
    }
    // 將上面的  n-1 個圓盤移到 B
    hanoid(n-1, a, c, b);
    // 此時將 A 底下的那塊最大的圓盤移到 C
    move(a, c);
    // 再將 B 上的 n-1 個圓盤經由A移到 C上
    hanoid(n-1, b, a, c);
}

public void move(char a, char b) {
    printf("%c->%c\n", a, b);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章