python 遞歸函數

遞歸函數

閱讀: 115067編程

在函數內部,能夠調用其餘函數。若是一個函數在內部調用自身自己,這個函數就是遞歸函數。數據結構

舉個例子,咱們來計算階乘n! = 1 x 2 x 3 x ... x n,用函數fact(n)表示,能夠看出:編程語言

fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n函數

因此,fact(n)能夠表示爲n x fact(n-1),只有n=1時須要特殊處理。優化

因而,fact(n)用遞歸的方式寫出來就是:spa

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

上面就是一個遞歸函數。能夠試試:code

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

若是咱們計算fact(5),能夠根據函數定義看到計算過程以下:遞歸

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

遞歸函數的優勢是定義簡單,邏輯清晰。理論上,全部的遞歸函數均可以寫成循環的方式,但循環的邏輯不如遞歸清晰。編譯器

使用遞歸函數須要注意防止棧溢出。在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。能夠試試fact(1000)it

>>> fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded

解決遞歸調用棧溢出的方法是經過尾遞歸優化,事實上尾遞歸和循環的效果是同樣的,因此,把循環當作是一種特殊的尾遞歸函數也是能夠的。

尾遞歸是指,在函數返回的時候,調用自身自己,而且,return語句不能包含表達式。這樣,編譯器或者解釋器就能夠把尾遞歸作優化,使遞歸自己不管調用多少次,都只佔用一個棧幀,不會出現棧溢出的狀況。

上面的fact(n)函數因爲return n * fact(n - 1)引入了乘法表達式,因此就不是尾遞歸了。要改爲尾遞歸方式,須要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

能夠看到,return fact_iter(num - 1, num * product)僅返回遞歸函數自己,num - 1num * product在函數調用前就會被計算,不影響函數調用。

fact(5)對應的fact_iter(5, 1)的調用以下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾遞歸調用時,若是作了優化,棧不會增加,所以,不管多少次調用也不會致使棧溢出。

遺憾的是,大多數編程語言沒有針對尾遞歸作優化,Python解釋器也沒有作優化,因此,即便把上面的fact(n)函數改爲尾遞歸方式,也會致使棧溢出。

小結

使用遞歸函數的優勢是邏輯簡單清晰,缺點是過深的調用會致使棧溢出。

針對尾遞歸優化的語言能夠經過尾遞歸防止棧溢出。尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能經過尾遞歸實現循環。

Python標準的解釋器沒有針對尾遞歸作優化,任何遞歸函數都存在棧溢出的問題。

相關文章
相關標籤/搜索