資料結構與算法系列九(遞迴詳解)
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