資料結構與演算法解析 「遞迴」篇

2021-10-07 06:22:18 字數 2363 閱讀 5561

遞迴,在數學與電腦科學中,是指在函式的定義中使用函式自身的方法。也就是說,遞迴演算法是一種直接或者間接呼叫自身函式或者方法的演算法。

遞迴是一種應用非常廣泛的演算法(或者程式設計技巧)。很多資料結構和演算法的編碼實現都要用到遞迴,比如 dfs 深度優先搜尋、前中後序二叉樹遍歷等等。

去的過程叫「遞」,回來的過程叫「歸」。基本上,所有的遞迴問題都可以用遞推公式來表示。

諸如

f

(n)=

f(n-1)

+1 其中,f(1)=1

用**表示為

intf(

int n)

那究竟什麼樣的問題可以用遞迴來解決呢?

只要同時滿足以下三個條件,就可以用遞迴來解決。

重點!!!:寫遞迴**最關鍵的是寫出遞推公式,找到終止條件,剩下將遞推公式轉化為**就很簡單了。

來個例子

假如這裡有 n 個台階,每次你可以跨 1 個台階或者 2 個台階,請問走這 n 個台階有多少種走法?

可以根據第一步的走法把所有走法分為兩類,第一類是第一步走了 1 個台階,另一類是第一步走了 2 個台階。所以 n 個台階的走法就等於先走 1 階後,n-1 個台階的走法 加上先走 2 階後,n-2 個台階的走法。用公式表示就是:

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

有了遞推公式,遞迴**基本上就完成了一半。我們再來看下終止條件。當有乙個台階時,我們不需要再繼續遞迴,就只有一種走法。所以 f(1)=1。這個遞迴終止條件足夠嗎?我們可以用 n=2,n=3 這樣比較小的數試驗一下。

n=2 時,f(2)=f(1)+f(0)。如果遞迴終止條件只有乙個 f(1)=1,那 f(2) 就無法求解了。所以除了 f(1)=1 這乙個遞迴終止條件外,還要有 f(0)=1,表示走 0 個台階有一種走法,不過這樣子看起來就不符合正常的邏輯思維了。所以,我們可以把 f(2)=2 作為一種終止條件,表示走 2 個台階,有兩種走法,一步走完或者分兩步來走。

所以,遞迴終止條件就是 f(1)=1,f(2)=2。這個時候,你可以再拿 n=3,n=4 來驗證一下,這個終止條件是否足夠並且正確。

我們把遞迴終止條件和剛剛得到的遞推公式放到一起就是這樣的:

f(1) = 1;

f(2) = 2;

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

有了這個公式,我們轉化成遞迴**就簡單多了。最終的遞迴**是這樣的:

int f(int n)

寫遞迴**的關鍵就是找到如何將大問題分解為小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成**。遞迴比較難的地方是,當遞迴呼叫只有乙個分支,也就是說「乙個問題只需要分解為乙個子問題」,我們很容易能夠想清楚「遞「和」歸」的每乙個步驟,所以寫起來、理解起來都不難。

但是,當我們面對的是乙個問題要分解為多個子問題的情況,遞迴**就沒那麼好理解了。

對於分解成多個子問題的情況,人腦幾乎沒辦法把整個「遞」和「歸」的過程一步一步都想清楚。

計算機擅長做重複的事情,所以遞迴正和它的胃口。而我們人腦更喜歡平鋪直敘的思維方式。當我們看到遞迴時,我們總想把遞迴平鋪展開,腦子裡就會迴圈,一層一層往下調,然後再一層一層返回,試圖想搞清楚計算機每一步都是怎麼執行的,這樣就很容易被繞進去。

對於遞迴**,這種試圖想清楚整個遞和歸過程的做法,實際上是進入了乙個思維誤區。很多時候,我們理解起來比較吃力,主要原因就是自己給自己製造了這種理解障礙。

那正確的思維方式應該是怎樣的呢?

如果乙個問題 a 可以分解為若干子問題 b、c、d,你可以假設子問題 b、c、d 已經解決,在此基礎上思考如何解決問題 a。而且,你只需要思考問題 a 與子問題 b、c、d 兩層之間的關係即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。遮蔽掉遞迴細節,這樣子理解起來就簡單多了。

因此,編寫遞迴**的關鍵是,只要遇到遞迴,我們就把它抽象成乙個遞推公式,不用想一層層的呼叫關係,不要試圖用人腦去分解遞迴的每個步驟。

遞迴有利有弊,利是遞迴**的表達力很強,寫起來非常簡潔;而弊就是空間複雜度高、有堆疊溢位的風險、存在重複計算、過多的函式呼叫會耗時較多等問題。所以,在開發過程中,我們要根據實際情況來選擇是否需要用遞迴的方式來實現。

一般來說:遞迴**一般可以使用,迭代迴圈的非遞迴寫法

爬樓梯在不使用遞迴的**如下|:

nt f

(int n)

return ret;

}

資料結構與演算法 基礎演算法篇 遞迴

遞迴是一種非常高效 簡潔的編碼技巧,一種應用非常廣泛的演算法,比如dfs深度優先搜尋 前中後序二叉樹遍歷等都是使用遞迴。方法或函式呼叫自身的方式稱為遞迴呼叫,呼叫稱為遞,返回稱為歸。基本上,所有的遞迴問題都可以用遞推公式來表示,比如 f n f n 1 1 f n f n 1 f n 2 f n n...

資料結構與演算法解析 棧篇

後進者先出,先進者後出,這就是典型的 棧 結構。從棧的操作特性上來看,棧是一種 操作受限 的線性表,只允許在一端插入和刪除資料。但這種受限,也控制了出錯的概率。當某個資料集合只涉及在一端插入和刪除資料,並且滿足後進先出 先進後出的特性,我們就應該首選 棧 這種資料結構。從棧的定義看,棧主要包含兩個操...

資料結構與演算法解析 佇列篇

先進者先出,這就是典型的 佇列 佇列跟棧非常相似,支援的操作也很有限,也是一種操作受限的線性表資料結構。最基本的操作也是兩個 跟棧一樣,佇列可以用陣列來實現,也可以用鍊錶來實現。用陣列實現的棧叫作順序棧,用鍊錶實現的棧叫作鏈式棧。同樣,用陣列實現的佇列叫作順序佇列,用鍊錶實現的佇列叫作鏈式佇列。2....