1. 程式人生 > >輕鬆搞懂Python遞迴函式的原理與應用

輕鬆搞懂Python遞迴函式的原理與應用

遞迴: 在函式的定義中,函式內部的語句呼叫函式本身。

1、遞迴的原理

學習任何計算機語言過程中,“遞迴”一直是所有人心中的疼。不知你是否聽過這個冷笑話:“一個麵包,走著走著餓了,於是就把自己吃了”。

呵呵。

 

 

 

常理推斷,特別是解釋型語言,當程式執行函式內部的語句時,這個函式還沒有定義完,沒定義完怎麼可以呼叫本身呢。

但實質上,當你執行函式內部的語句時,一定有函式外部的語句呼叫了這個函式,此時該函式的所有程式碼和語句,已經在記憶體中形成了邏輯,這就是遞迴函式的原理。

在Python當中很重要的就是遞迴的使用。

2、遞迴的玩法

牛叔語錄:人類創造出的任何複雜的概念,都是為了讓事情變得更簡單一些。

遞迴是為了更好的解決,“自已定義自己”的數學或是哲學難題。

用好遞迴,有2個、2個、2個(重要的事情說3遍)要點,必須要同時注意,下面請各位評委看我的表演。

(1) 認清自己

認清自己是遞迴玩法的核心。與處理任何事情一樣,首先要問的就是:如何準確地定義好“當前要做的事情”。處理好這個問題,我們就已經成功了一半。

此處我們拿數學函式為例, 因為數學函式的定義有公式,顯而易見,它的意義就是根據變數來計算出結果,最容易被小白理解,栗子如下:

假設數學函式:

f(n) = (f(n-1) + 1)*2 ,告訴你f(1)=1,問f(10)是多少?

我們直接把這個f(n)定義成函式,並且計算f(10) 如下:

def f(n):
    if n==1:
        return 1
    else:
        return (f(n-1)+1)*2

print(f(10))

結果是1534,執行正確。恭喜你這是一道著名的數學難題,你竟然輕鬆解決了,原題這樣:

猴子有一堆桃子,每天吃前一天剩下的一半多1個,昨天吃完發現剩了1個,那麼前10天它剩下多少個桃子?

我們公式中的n就表示前n天,公式結果就表示剩下了多少桃子,昨天的桃子y總比今天的x有這樣的關係:x = y/2 -1 所以:y=(x+1)*2,這個就是上面的公式。

在寫本遞迴函式時,語句基本上照抄數學公式,我們不知道解題過程就能求出答案,真是太神奇了!只要定義好,遞迴跑不了!慢著,別下結論,我們再看看如下的點。

(2)把握好方向

與上面的“猴子摘桃”同一個問題,原題我們改一下說法,問:

猴子第1天摘了一堆桃子吃了一半又多一個,第2天吃了剩下的一半又多一個,...,第10天早上時發現只有1個桃子了。問第1天摘了多少?這回我們把n設為第n天(而不是前n天)

與上面函式f類似,後項也是根據前項值來確定,為區別我們使用g()代表該函式:

g(n) = g(n-1)/2 -1 ,告訴你 g(10)=1,問g(1) 是多少?

牛叔說了,“只要定義好,遞迴跑不了”,我們先不管三七二十一,照抄上面的數學公式,“迅速而又準確”地把g(n)函式用如下的程式碼造了出來,如下:

def g(n):
    if n==10:
        return 1
    else:
        return g(n-1)/2 - 1

print(g(1))

 執行後卻出現瞭如下的錯誤,提示遞迴溢位!

 這是怎麼回事呢?因為我們還有第2個點,您沒有注意!

這個點就是 “要想遞迴不出錯,開車方向不能錯”,這個方向是指: 程式求解的方向必須和定義的方向相同,

此處我們要根據n=10的結果來求n=1,方向是:後項(10)->前項(1),而程式中翻譯的公式是 前項(n-1)->後項(n),根據公式寫的程式,也只能完成公式的求解順序,即:n從小到大的推導,

所以此時如果求g(100),結果是這樣的,完全沒有問題(但沒有任何意義):

-2.0

所以此處要通過我們的智慧把這個公式,“顛倒”過來變成:

g(n-1) = (g(n)+1) * 2    => g(n) = (g(n+1)+1) * 2 

這樣根據公式寫的程式碼,就可以完成從後項(n+1)推導到前項(n)的功能了! 

如果不理解,小學二年級的代數再看一遍,具體程式如下:

1 def g(n):
2     if n==10:
3         return 1
4     else:
5         return 2*(g(n+1)+1)
6 
7 print(g(1))

 

終於得出了正確的結果,1534。答案和第1題一樣!猴子終於滿意了,10天的口糧有著落了。 

3、分解質因數練習

小學生專用名詞,別給嚇住了,其實就是把數字分解成不能再分解的乘法,比如:8=2*2*2, 10 = 2*5,而不是 8 = 2 * 4 這種可以再分解的。

此遞迴問題,是程式設計競賽常客,拿來練手比較合適。如下Python程式碼,對於本問題的解答充分說明了遞迴的方便之處,以分解了980這個整數為示例:

1 def defactor(N):  #定義一個函式名稱為defactor,意義是返回N的所有因子
2     for i in range(2,N): #從2開始試試
3         if N%i ==0: #如果試到i是N的因子的話,就返回i的所有因子和N/i的所有因子 的列表
4             return defactor(i)+defactor(int(N/i))
5     else:#如果沒有試到就說明這個N是一個質數,就直接包含它的 列表
6         return [N]
7         
8 print(defactor(980))

 

執行的答案是:

[2, 2, 5, 7, 7]

Bingo,在這裡函式defactor(N) 

(1)定義:  求出引數N所有因子的陣列,

(2)方向:整數 -> 最小的質數

首先,理解else:部分(程式碼中的第5,6兩行),

這是函式的,把方向的部分,小牛叔稱他為:結果產生部分,它能確保程式不會跑偏的程式碼,如果傳入的數字N沒有可以被整除的數字,即迴圈到頭了,才會執行這個部分,每一個質數因子都會從這個部分產生,因為最終的結果只能是質數。下圖樹中的所有葉節點(沒有子節點的葉子:2,2,5,7,7),就是從這個部分產生的,當執行到這個部分時,程式不會再往向下遞迴,所以不會產生下層的樹型結構。

再理解,遞迴的部分(2,3,4行)

通過迴圈來試因子,如果試到i是N的因子(即N可以被i整除)的話,就返回i的所有因子和 N/i的所有因子 組成的列表,因此下圖中所有從上到下的兩個箭頭對,就代表著這兩個遞迴呼叫。

通過這個樹形解題的過程,您會更加明白!

 

 

在整個程式遞迴執行時,看起來是從頂980開始向下求解執行,而求解的過程中結果卻是從最下層的7,7開始向上匯