遞歸算法總結

 

概念

遞歸算法遞歸算法是算法中最基礎,入門級別的算法,簡單理解:不停直接或間接調用自身函數,每次調用會改變一個或者多個變量,直到變量到達邊界,結束調用。html

借用知乎上Memoria的回答:算法

假設你在一個電影院,你想知道本身坐在哪一排,可是前面人不少,你懶得去數了,因而你問前一排的人「你坐在哪一排?」,這樣前面的人 (代號 A) 回答你之後,你就知道本身在哪一排了——只要把 A 的答案加一,就是本身所在的排了。不料 A 比你還懶,他也不想數,因而他也問他前面的人 B「你坐在哪一排?」,這樣 A 能夠用和你如出一轍的步驟知道本身所在的排。而後 B 也如法炮製。直到他們這一串人問到了最前面的一排,第一排的人告訴問問題的人「我在第一排」。最後你們就都知道本身在哪一排了。編程

正規的說法:遞歸算法就是經過不斷調用自身將原問題分解爲跟原問題相同解決方法的子問題,最後將各子問題的解合併獲得原問題的解。遞歸的核心思想是分治策略,也就是將一個規模大的問題分解成一些規模小的同類問題,而後經過這些小問題求得大問題的解。函數

遞歸算法和分治法的區別:遞歸是算法的實現方式,分治是算法的設計思想。 這跟你吃飯能夠用筷子吃,也用手吃同樣。也就是我使用的是分治法的思想,但不必定使用遞歸算法來實現該實現。優化

遞歸算法和循環的區別:遞歸算法是將問題規模縮小,最終獲得問題的解;而循環是一種由遠變到近的過程,問題的規模不見得縮小了,可是慢慢在調整接近答案。spa

遞歸算法的設計

  1. 找出遞歸的終止條件。
  2. 終止後要返回什麼結果。
  3. 遞歸部分。

好比上面的電影院例子:.net

  1. 當到了第一排就終止;
  2. 在第一排返回 本身是坐哪一排的信息;
  3. 從我開始要知道本身的位置,我須要知道我前一排的位置而後再加一獲得個人位置,而我前一排鬚要他前一排的位置加一獲得他的位置。這就是遞歸部分。

遞歸算法的優缺點

遞歸算法的時間複雜度是O(n^2),因此也是比較暴力破解算法。設計

優勢:只須要幾條代碼就能夠解決問題。code

缺點:htm

  1. 有時由於太過簡潔而難理解過程。
  2. 遞歸會保存大量臨時數據和重複的數據,太多的話,會形成棧溢出,程序崩潰。
  3. 數據規模大時,運行時間會超時。因此作編程題時,注意看數據規模的大小,最好別用遞歸。

經典例題

  1. 求解Fibonacci數列的第n個位置的值?(斐波納契數列(Fibonacci Sequence),又稱黃金分割數列,指的是這樣一個數列:一、一、二、三、五、八、1三、2一、
    ……在數學上,斐波納契數列以以下被以遞歸的方法定義:F1=1,F2=1,Fn=F(n-1)+F(n-2)(n>2,n∈N*))。

設計步驟:

1.找出終止條件:當n=1時;當n=2時
2.終止後要返回什麼結果:當n=1時,F1=1;當n=2時,F2=1
3.遞歸部分:Fn=F(n-1)+F(n-2)(n>2,n∈N*)),也就是一個數會等於前兩個數相加的結果。

代碼:

public class Fibonacci {
    public static int fib(int n){
        // 1. 終止條件
        if(n == 1 || n == 2){
            // 2.終止完要返回什麼
            return 1;
        }
        // 3.遞歸部分
        return fib(n-1) + fib(n-2);
    }
    public static void main(String[] args) {
        System.out.println(fib(10));
    }
}

 

能夠試試你的n很大的話(n=100便可感覺到),這段代碼會運行半天還沒算出結果,這就是遞歸算法的缺點。用動態規劃也能夠作並且更好更快計算,不過如今這裏是學遞歸算法。後面到動態規劃會拿出來優化。

過程圖:

在這裏插入圖片描述

如圖能夠看到,像F(4)和F(3)重複計算了幾回,也就致使要多些運算時間和內存空間。

  1. 階乘

階乘的公式直接推:n!=n*(n-1)*(n-2)…3*2*1

終止條件就是當n=1時返回1。

核心代碼:

    public static int factorial(int n){
        if(n == 1){
            return 1;
        }
        return n*factorial(n-1);
    }

 

過程圖:
在這裏插入圖片描述

  1. 漢諾塔問題是一個經典的遞歸問題。漢諾塔(Hanoi Tower),又稱河內塔,源於印度一個古老傳說。大梵天創造世界的時候作了三根金剛石柱子,
    在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序從新擺放在另外一根柱子上。而且規定,任什麼時候候,在小圓盤上都不能放大圓盤,且在三根柱子之間一次只能移動一個圓盤。假設咱們須要這些盤片從A柱移動到C柱,應該如何操做?
    在這裏插入圖片描述

解決:
主要是利用漢諾塔理解下遞歸的思想。

來分析它的步驟:(->表示移動)

如今假設只有一個盤片,那麼直接就是:A->C
在這裏插入圖片描述

假設如今有兩個盤片,那麼就須要三步:

  1. 從A柱最頂的盤片移動到B柱:A->B
  2. 而後A柱如今只剩最底的盤片,也是最大的盤片,移動到C柱:A->C
  3. 最後B柱上的盤片移動到C:B->C

對於此三步,咱們再解析一下:

  • 當第一步A->B完成後,其實問題就變回了當在A柱上只有一個盤片時怎麼操做的問題,由於此時A柱就剩最底的一個盤片啊,因此跟上面只有一個盤片的解決方式同樣,直接A->C。
  • 那麼第三步呢?第三步其實也是跟只有一個盤片的解決方式同樣,只不過如今是在B柱,那麼你能夠當作,是B->A->C,也就是把B柱的盤片先移動到A柱,而後再從A柱移動到C柱,這樣對結果並無影響而且只是作個思想轉換,並且當從A柱移動到C柱,又回到了只有一個盤片的解決方式同樣。
    在這裏插入圖片描述

當有三個盤片時,咱們把三個盤片從上到下編號1,2,3。想要讓A柱編號3的盤片移動到C柱,必須先移開A柱前兩個盤片,而後才能夠把A柱編號3的盤片移動到C柱。咱們能夠直接把前兩個當成一個總體直接移動到B柱,不在乎怎麼移的,而後把編號3的盤片移動到C柱。如圖:
在這裏插入圖片描述
經過這張圖可得,目前咱們已經解決了移動編號3的盤片的問題,那麼剩下的問題就是解決在B柱上的兩個盤片該如何操做?這不就是轉變成只有兩個盤片時該如何移動的問題了嗎?雖然如今是在B柱上,那麼這跟剛剛有兩個盤片的操做同樣。

後面四個盤片五個盤片…n個盤片都是同樣的遞歸思想,至此對於漢諾塔的遞歸思想應該是有頭緒了。

總結:
對於有n個盤片的漢諾塔,咱們把n-1個盤片當成一個總體移動到B柱(不用去管細節,怎麼移動的,反正移到最後一步確定是這樣的結果,可是得知道,確定是藉助C柱來把這些盤片從A柱移動到B柱,也就是會將其中的柱做爲輔助柱來輔助移動,這句等下能夠理解代碼),而後把第n個盤片移動到C柱,最後繼續解決n-1個盤片如何移動的問題。一直到n=1時,直接A->C。

由於解決F(N)以前解決F(N-1),而解決F(N-1)以前解決F(N-2),直到n=1,因此遞歸時首先打印的就是n=1時的移動,而後一直一直回退打印,直到最後打印F(N)。跟前兩道的例題運行流程圖同樣。

代碼實現:

public class HanoiTower {
    /**
     *
     * @param n 盤片數
     * @param a 柱子A
     * @param b 柱子B
     * @param c 柱子C
     * @return 移動步驟
     */
    public static void hanoi(int n, char a, char b, char c){
        // 終止條件
        if(n == 1){
            // 剩最後一個盤片時直接從A移動到C
            System.out.println(a + "->" + c);
        } else {
            // 第一步,把n-1當成總體,從A移動到B,以C柱做爲輔助柱來輔助移動,但咱們只須要移動後的結果。
            hanoi(n-1, a, c, b);
            // 第二步,剩最後一個盤片時直接從A移動到C
            System.out.println(a + "->" + c);
            // 第三步,繼續解決n-1的問題,此時的問題變成在B柱移動到C柱如何操做
            // 從B移動到C,以A柱做爲輔助柱
            hanoi(n-1, b, a, c);
        }
    }

    public static void main(String[] args) {
        hanoi(3,'A','B','C');
    }

}

當n=3時,它的運行結果是:

A->C
A->B
C->B
A->C
B->A
B->C
A->C

會疑惑,第一步是A->C???而不是A->B???
正如前面所說,遞歸是解決F(N)以前解決F(N-1),而解決F(N-1)以前解決F(N-2),而且咱們是將前兩片當成一個總體的思路移動到B柱,其實是:編號1的盤片先從A移動到C,而後編號2的盤片從A移動到B,最後編號1的盤片從C移動到B。

我講得好囉嗦,不過應該完全明白了。

因此在設計遞歸算法時是不須要去在乎細節,咱們把F(N-1)看成一個總體,去解決F(N)。

參考:
烏梟的遞歸整理
漢諾塔

相關文章
相關標籤/搜索