遞迴階層 C 個人學習筆記 掌握遞迴解題

2021-10-14 16:17:21 字數 3709 閱讀 3940

遞迴是程式設計和演算法學習中經常遇到的一類問題,遞迴解題本身也是很多建模比賽和面試題中常用的一種解題方法,本文將對遞迴解題作較為系統的介紹,並帶有例項說明。

遞迴是指在函式中呼叫函式本身的現象

以階乘函式為例, 在 factorial 函式中存在著 factorial(n - 1) 的呼叫,所以此函式是遞迴函式。

int factorial(int n) 

進一步解釋遞迴問題。「遞」的意思是將問題拆解成子問題來解決, 子問題再拆解成子子問題,.....,直到被拆解的子問題無需再拆分成更細的子問題(即可以求解),「歸」是說最小的子問題解決了,那麼它的上一層子問題也就解決了,上一層的子問題解決了,上上層子問題自然也就解決了,....,直到最開始的問題解決,文字說可能有點抽象,那我們就以階層 f(6) 為例來看下它的「遞」和「歸」。

求解問題 f(6), 由於 f(6) = n * f(5), 所以 f(6) 需要拆解成 f(5) 子問題進行求解,同理 f(5) = n * f(4) ,也需要進一步拆分,... ,直到 f(1), 這是「遞」,f(1) 解決了,由於 f(2) = 2 f(1) = 2 也解決了,.... f(n)到最後也解決了,這是「歸」,所以遞迴的本質是能把問題拆分成具有相同解決思路的子問題,......,直到最後被拆解的子問題再也不能拆分,解決了最小粒度可求解的子問題後,在「歸」的過程中自然順其自然地解決了最開始的問題。

根據以上分析,不難發現遞迴問題有以下特點:

乙個問題可分解成具有相同解決思路的子問題、子子問題,即這些問題都可呼叫同乙個函式。經過層層分解的子問題最後一定有乙個不能再分解的固定值(即終止條件),如果沒有的話就無窮無盡地分解子問題,問題顯然是無解的。

解遞迴題的關鍵在於我們首先需要根據以上特點判斷題目是否可以用遞迴來解。經判斷可以用遞迴後,接下來看看用遞迴解題的基本套路:

(1)先定義乙個函式,明確這個函式的功能,由於遞迴的特點是問題和子問題都會呼叫函式自身,所以這個函式的功能一旦確定了, 之後只要找尋問題與子問題的遞迴關係即可。

(2)接下來尋找問題與子問題間的關係(即遞推公式),由於問題與子問題具有相同解決思路,只要子問題呼叫步驟 1 定義好的函式,問題即可解決。所謂的關係最好能用乙個公式表示出來,比如f(n) = n * f(n-)這樣,如果暫時無法得出明確的公式,用偽**表示也是可以的, 發現遞推關係後,要尋找最終不可再分解的子問題的解,即(臨界條件),確保子問題不會無限分解下去。由於第一步我們已經定義了這個函式的功能,所以當問題拆分成子問題時,子問題可以呼叫步驟 1 定義的函式,符合遞迴的條件(函式裡呼叫自身)。

(3)將第二步的遞推公式用**表示出來補充到步驟 1 定義的函式中。

(4)最後也是很關鍵的一步,根據問題與子問題的關係,推導出時間複雜度,如果發現遞迴時間複雜度不可接受,則需轉換思路對其進行改造,看下是否有更靠譜的解法。

3.1 輸入乙個正整數n,輸出n!的值。其中n!=123*…*n,即求階乘。

(1)定義這個函式,明確這個函式的功能,我們知道這個函式的功能是求 n 的階乘, 之後求 n-1, n-2 的階乘就可以呼叫此函式了。

int factorial(int n) 

(2)尋找問題與子問題的關係,階乘關係比較簡單, 以 f(n) 來表示 n 的階乘, 顯然 f(n) = n * f(n - 1), 同時臨界條件是 f(1) = 1。

(3)將第二步的遞推公式用**表示出來補充到步驟 1 定義的函式中。

int factorial(int n) 

// 第二步的遞推公式

return n * factorial(n-1)

}

(4)求時間複雜度 由於 f(n) = n * f(n-1) = n * (n-1) * .... * f(1),總共作了 n 次乘法,所以時間複雜度為 n。

3.2 乙隻青蛙可以一次跳 1 級台階或一次跳 2 級台階,例如:跳上第 1 級台階只有一種跳法:直接跳 1 級即可。跳上第 2 級台階有兩種跳法:每次跳 1 級,跳兩次;或者一次跳 2 級。問要跳上第 n 級台階有多少種跳法?

(1)定義乙個函式,這個函式代表了跳上 n 級台階的跳法。

public int f(int n) 

(2)尋找問題與子問題之前的關係 這兩者之前的關係初看確實看不出什麼頭緒,但仔細看題目,乙隻青蛙只能跳一步或兩步台階,自上而下地思考,也就是說如果要跳到 n 級台階只能從 從 n-1 或 n-2 級跳, 所以問題就轉化為跳上 n-1 和 n-2 級台階的跳法了,如果 f(n) 代表跳到 n 級台階的跳法,那麼從以上分析可得 f(n) = f(n-1) + f(n-2),顯然這就是我們要找的問題與子問題的關係,而顯然當 n = 1, n = 2, 即跳一二級台階是問題的最終解。

(3)將第二步的遞推公式用**表示出來補充到步驟 1 定義的函式中 補充後的函式如下

int f(int n) 

(4)計算時間複雜度。斐波那契的時間複雜度計算涉及到高等代數的知識,直接給出結論時間複雜度是指數級別。關於為什麼時間複雜度這麼大,自習分析可以看到遞迴過程中有大量的重複計算, f(3) 計算了 3 次, 隨著 n 的增大,f(n) 的時間複雜度自然呈指數上公升了。

(5)優化

既然有這麼多的重複計算,我們可以想到把這些中間計算過的結果儲存起來,如果之後的計算中碰到同樣需要計算的中間態,直接在這個儲存的結果裡查詢即可,這就是典型的以空間換時間,改造後的**如下:

int f(int n) 

那麼改造後的時間複雜度,由於對每乙個計算過的 f(n) 我們都儲存了中間態 ,不存在重複計算的問題,所以時間複雜度是 o(n)。但由於用了乙個鍵值對來儲存中間的計算結果,所以空間複雜度是 o(n)。

(6)進一步優化空間複雜度。使用迴圈迭代來改造演算法 我們在分析問題與子問題關係(f(n) = f(n-1) + f(n-2))的時候用的是自頂向下的分析方式,但其實我們在解 f(n) 的時候可以用自下而上的方式來解決,通過觀察我們可以發現以下規律:

f(1) = 1

f(2) = 2

f(3) = f(1) + f(2) = 3

f(4) = f(3) + f(2) = 5

....

f(n) = f(n-1) + f(n-2)

最底層 f(1), f(2) 的值是確定的,之後的 f(3), f(4) ,...等問題都可以根據前兩項求解出來,一直到 f(n)。所以我們的**可以改造成以下方式:

int f(int n) 

return result;

}

改造後的時間複雜度是 o(n), 而由於我們在計算過程中只定義了兩個變數(pre,next),所以空間複雜度是o(1)。

分析遞迴問題時我們需要採用自上而下的思維,而解決問題有時候採用自下而上的方式能讓演算法效能得到極大提公升。若要熟練掌握還需要進行一些題目的訓練才可以。

C語言個人學習筆記

在匯程式設計序中,乙個函式的開頭常常見到以下 8048cbd 83 ec 18 sub 0x18,esp這句話的目的是分配棧幀。棧幀中存放的是程式中的區域性變數。在windows的編譯器cl.exe中這些棧幀中的值最初會被初始化為0xcc,這也是為什麼vc 在陣列越界或訪問未賦值的記憶體時列印 燙燙...

C 遞迴學習筆記

這篇筆記是對慕課上郭煒老師的c 課程自己的總結,方便日後複習 重點 將問題分解為規模更小的子問題解決 如 1.爬樓梯問題,乙個人每次走1級或2級,輸入樓梯級數求不同的走法數 n級台階走法 先走一級後,n 1級的台階走法 先走兩級後,n 2級台階走法f n f n 1 f n 2 完整 include...

java基礎 個人學習筆記 C

12.class valuetest1 intnum 10 for strings student system.out.println 方法呼叫前 t student 0 范冰冰 changevalue student system.out.println 方法呼叫後 t student 0 周杰...