咱們先來說一個故事,講的什麼呢?從前有座山,山裏有座廟,廟裏有個老和尚講故事,講的什麼呢?從前有座山,山裏有座廟,廟裏有個老和尚講故事,講的什麼呢?從前有座山,山裏有座廟,廟裏有個老和尚講故事,講的什麼呢?從前有座山,山裏有座廟,廟裏有個老和尚講故事,講的什麼呢.....python
遞歸函數表示以下:編程
def story(): s = """ 從前有個山,山裏有座廟,廟裏老和尚講故事, 講的什麼呢? """ print(s) story() story() # 遞歸函數有最大遞歸深度,超過便會報錯
在函數內部,能夠調用其餘函數。若是一個函數在內部調用自身自己,這個函數就是遞歸函數。數據結構
遞歸函數若是不受外力的阻止會一直執行下去。可是咱們以前已經說過關於函數調用的問題,每一次函數調用都會產生一個屬於它本身的名稱空間,若是一直調用下去,就會形成名稱空間佔用太多內存的問題,因而python爲了杜絕此類現象,強制的將遞歸層數控制在了997。編程語言
來看例子:函數
age(4) = age(3) + 2 age(3) = age(2) + 2 age(2) = age(1) + 2 age(1) = 40 # 遞歸函數 def age(n): if n == 1: return 40 else: return age(n-1)+2 print(age(4))
計算階乘:n! = 1x2x3x......xn 用函數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時須要特殊處理。 # 遞歸函數 def fact(n): if n == 1: return 1 return fact(n-1) * n print(fact(3))
遞歸函數的優勢是定義簡單,邏輯清晰。理論上,全部的遞歸函數均可以寫成循環的方式,但循環的邏輯不如遞歸清晰。優化
使用遞歸函數須要注意防止棧溢出。在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出。能夠試試fact(1000)
:spa
>>> 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
解決遞歸調用棧溢出的方法是經過尾遞歸優化,事實上尾遞歸和循環的效果是同樣的,因此,把循環當作是一種特殊的尾遞歸函數也是能夠的。code
尾遞歸是指,在函數返回的時候,調用自身自己,而且,return語句不能包含表達式。這樣,編譯器或者解釋器就能夠把尾遞歸作優化,使遞歸自己不管調用多少次,都只佔用一個棧幀,不會出現棧溢出的狀況。blog
上面的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 - 1
和num * 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標準的解釋器沒有針對尾遞歸作優化,任何遞歸函數都存在棧溢出的問題。