1. 程式人生 > >資料結構與算法系列九(遞迴詳解)

資料結構與算法系列九(遞迴詳解)

1.引子

1.1.為什麼要學習資料結構與演算法?

有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀!

有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎?

於是問題來了:為什麼還要學習資料結構與演算法呢?

#理由一:
    面試的時候,千萬不要被資料結構與演算法拖了後腿
#理由二:
    你真的願意做一輩子CRUD Boy嗎
#理由三:
    不想寫出開源框架,中介軟體的工程師,不是好廚子

1.2.如何系統化學習資料結構與演算法?

我想好了,還是需要學習資料結構與演算法。但是我有兩個困惑:

1.如何著手學習呢?

2.有哪些內容要學習呢?

學習方法推薦:

#學習方法
1.從基礎開始,系統化學習
2.多動手,每一種資料結構與演算法,都自己用程式碼實現出來
3.思路更重要:理解實現思想,不要背程式碼
4.與日常開發結合,對應應用場景

學習內容推薦:

資料結構與演算法內容比較多,我們本著實用原則,學習經典的、常用的資料結構、與常用演算法

#學習內容:
1.資料結構的定義
2.演算法的定義
3.複雜度分析
4.常用資料結構
    陣列、連結串列、棧、佇列
    散列表、二叉樹、堆
    跳錶、圖
5.常用演算法
    遞迴、排序、二分查詢
    搜尋、雜湊、貪心、分治
    動態規劃、字串匹配

2.考考你

在上一篇【資料結構與算法系列八(遞迴見面禮)】中,通過兩個小案例:

1.求最終推薦人

2.電影院看電影

相信你已經對遞迴有一個直觀的認識了。那麼這一篇我們來對遞迴做一個詳細的分析,比如以下這幾個問題,如果你都知道,那麼恭喜你!你都會搶答了(開個玩笑,這是黑土大爺說的:不是賣車,就是賣柺,對吧)。

言歸正傳,關於遞迴,我們只需要搞清楚以下幾個問題,就算是完全掌握了。很簡單有沒有?

#考考你:
1.你知道哪些問題適合用遞迴解決嗎?
2.你知道編寫遞迴程式碼的關鍵步驟嗎?

3.案例

3.1.遞迴解決的問題模板

我們說每一種資料結構與演算法,都是在特定問題域下的產物;也就是說每一種資料結構與演算法,都有它們各自適用的問題場景。

關於遞迴,結合上一篇:求最終推薦人、電影院看電影案例,我們可以歸納如下:

1.問題可以分解為子問題

簡述:

對於一個問題的求解,本身可以被分解為更小的子問題

案例:

電影院案例:我們在哪一排的問題,可以分解為我們的前一排人在哪一排的子問題

 

2.問題的求解,與分解後的子問題,求解思路一致

簡述:

問題本身的求解思路,與分解後的子問題求解思路一致

案例:

電影院案例:求解我們在哪一排的問題思路,與求解我們前一排人在哪一排的思路一致

 

3.問題的求解,存在終止條件

簡述:

注意,這一條很重要,遞迴一定要有明確的終止條件,否則就等於死迴圈了

案例:

電影院案例:存在終止條件,如果是第一排,則f(1)=1

 

3.2.編寫遞迴程式碼關鍵步驟

知道了遞迴適合解決的問題,那麼在實際軟體開發中,我們該如何更有效率的編寫遞迴程式碼呢?有沒有什麼套路,或者模板。答案是有。

編寫遞迴程式碼,有兩個關鍵步驟:

1.找出遞推公式

簡述:

前面我們說了,遞迴適合於將問題分解為子問題,然後問題本身的求解,與子問題求解思路一致。你需要仔細琢磨並理解這句話的含義,這句話是精髓。如果你理解了,你會發現這其實是一個重複性的問題。

那麼順著這個思路,我們只需要將子問題,在繼續分解問為更小的子問題......一直到子問題不能再分解為止。

這裡需要提醒一個常見的思維誤區:我們在分解問題的時候,千萬不要在大腦裡去重現每一個分解步驟,這樣容易把自己繞進去:走火入魔。畢竟人類的大腦只適合平鋪直敘的思維方式,重複性的東西更適合電腦,對吧。你只需要找出不能再分解的最小子問題,然後解決它就對了。

 

案例:

電影院案例:f(n)=f(n-1)+1

 

2.找出終止條件

簡述:

關於遞迴就是一個不斷分解子問題,與求解子問題的過程。因此一定要存在明確的終止條件。一定要找到它,不然就死迴圈了。

 

案例:

電影院案例:f(1)=1

3.3.走臺階案例

我們通過一個稍微複雜些的案例,來鞏固3.1與3.2兩節的內容。假設有一個n階的臺階,小明每一步可以走1個臺階,或者走2個臺階。求小明走完臺階,總共有多少種走法?

3.3.1.找出遞推公式

我們首先嚐試分析,有這麼幾個前提:

1.總檯階數是n

2.走臺階的方式,一次可以走1個臺階,或者一次可以走2個臺階

那麼根據遞迴求解過程:分解子問題。不過這個案例稍微複雜一些,我們可以假設小明第一步只走1個臺階,是一種走法,即 f(n-1);第一步走2個臺階,又是一種走法,即f(n-2)。

這樣一來,我們就可以得出遞推公式,走完n個臺階的走法f(n),等於第一步走1個臺階的走法f(n-1),加上第一步走2個臺階的走法f(n-2)。即:

#走n個臺階的遞推公式
f(n)=f(n-1)+f(n-2)

3.3.2.找出終止條件

尋找終止條件的前提是,假設子問題不能再分解為更小的子問題的時候,有明確的終止條件。我們繼續嘗試分析:

1.我們假設最後只剩下1個臺階的情況,只有一種走法,即:f(1)=1

2.我們假設最後剩下2個臺階的情況,一次可以走1個臺階,是一種走法;一次可以走2個臺階,又是一種走法。那麼剩下2個臺階的走法是:f(2)=2

#走n個臺階的終止條件
只剩下1個臺階:f(1)=1
剩下2個臺階:f(2)=2

3.3.3.實現程式碼

有了遞推公式,與終止條件,再來編寫遞迴程式碼就是水到渠成的事情了,很簡單了有沒有

/**
* 遞迴求解走n階抬價的走法:
*  1.求解遞推公式
*      1.1.前提:每次可以走1步臺階,或者2步臺階
*      1.2.第一步走法是關鍵:
*          1.2.1.如果第一步走1步臺階,則剩下n-1步臺階
*          1.2.2.如果第一步走2步臺階,則剩下n-2步臺階
*      1.3.根據1.2.1與1.2.2得出:
*          1.3.1.遞推公式:f(n) = f(n-1) + f(n-2)
*          1.3.2.f(n-1)表示剩下n-1步臺階走法
*          1.3.3.f(n-2)表示剩下n-2步臺階走法
*
*  2.求解終止條件
*      2.1.如果只剩下1步臺階,則f(1)=1
*      2.2.如果剩下2步臺階,則f(2)=2,有兩種走法
* @param n
*/
public static int walkingMethod(int n){
    // 終止條件f(1)=1
    if(n == 1) return 1;

    // 終止條件f(2)=2
    if(n == 2) return 2;

    return walkingMethod(n - 1) + walkingMethod(n -2);
}

4.討論分享

#考考你答案:
1.你知道哪些問題適合用遞迴解決嗎?
  1.1.如果一個問題,滿足三個條件,適合用遞迴解決
    a.問題本身,可以分解為子問題
    b.問題本身的求解思路,與分解後的子問題求解思路一致
    c.求解問題,本身存在明確的終止條件
     
2.你知道編寫遞迴程式碼的關鍵步驟嗎?
  2.1.編寫遞迴程式碼的關鍵步驟,有兩步
    a.根據分解後的子問題,找出遞推公式
    b.當子問題足夠小的時候,找出終止條件

&n